You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Service Worker时如何强制Flutter Web启用CanvasKit渲染器?

解决方案:Flutter Web BlendMode.clear兼容与CanvasKit+Service Worker配置

一、替代BlendMode.clear(兼容HTML渲染器)

你的自定义绘制代码用BlendMode.clear是为了实现带模糊效果的镂空圆角边框,可以用以下两种兼容HTML渲染器的方式替代:

方式1:裁剪+背景填充

通过裁剪中间区域,用背景色覆盖实现镂空,适合固定背景色的场景:

@override
void paint(Canvas canvas, Size size) {
  var center = Offset(size.width / 2, size.height / 2);
  var roundedRectangle = RRect.fromRectAndRadius(
    Rect.fromCenter(center: center, width: size.width, height: size.height),
    Radius.circular(borderRadius),
  );

  // 绘制带模糊的边框底层
  Paint blurPaint = Paint()
    ..color = color
    ..style = PaintingStyle.stroke
    ..strokeWidth = circleWidth
    ..maskFilter = MaskFilter.blur(BlurStyle.normal, blurSigma);
  canvas.drawRRect(roundedRectangle, blurPaint);

  // 裁剪中间区域,用背景色填充实现镂空(替换为实际背景色)
  canvas.save();
  canvas.clipRRect(roundedRectangle.deflate(circleWidth / 2));
  canvas.drawRect(Rect.largest, Paint()..color = Colors.white);
  canvas.restore();
}

方式2:BlendMode.dstOut混合模式

适合透明或动态背景的场景,dstOut是HTML渲染器支持的混合模式,作用与clear一致:

@override
void paint(Canvas canvas, Size size) {
  var center = Offset(size.width / 2, size.height / 2);
  var roundedRectangle = RRect.fromRectAndRadius(
    Rect.fromCenter(center: center, width: size.width, height: size.height),
    Radius.circular(borderRadius),
  );

  canvas.saveLayer(Rect.largest, Paint());
  // 绘制带模糊的边框
  Paint line = Paint()
    ..color = color
    ..strokeCap = StrokeCap.round
    ..style = PaintingStyle.stroke
    ..strokeWidth = circleWidth
    ..maskFilter = MaskFilter.blur(BlurStyle.normal, blurSigma);
  canvas.drawRRect(roundedRectangle, line);
  // 用dstOut模式挖空中间区域
  Paint erasePaint = Paint()..blendMode = BlendMode.dstOut;
  canvas.drawRRect(roundedRectangle.deflate(circleWidth / 2), erasePaint);
  canvas.restore();
}

二、修复CanvasKit+Service Worker的白屏问题

你之前的index.html存在逻辑错误:在loadMainDartJs里嵌套了window.addEventListener('load'),但页面load事件已触发,导致_flutter.loader.loadEntrypoint从未执行。修改后的正确配置如下:

<!DOCTYPE html>
<html>
<head>
    <base href="$FLUTTER_BASE_HREF">
    <meta charset="UTF-8">
    <meta content="IE=Edge" http-equiv="X-UA-Compatible">
    <meta name="description" content="App development that makes you happy.">
    <!-- iOS meta tags & icons -->
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black">
    <meta name="apple-mobile-web-app-title" content="website">
    <link rel="apple-touch-icon" href="icons/Icon-192.png">
    <!-- Favicon -->
    <link rel="icon" type="image/png" href="favicon.png"/>
    <title>Apphy</title>
    <link rel="manifest" href="manifest.json">
    <script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyD3d1bOcWTEPPFZIeCqofEv2ipL23bsJTU"></script>
    <script src="flutter.js" defer></script>
</head>
<body>
<!-- added for q_form package -->
<script src="assets/packages/libphonenumber_plugin/js/libphonenumber.js"></script>
<script src="assets/packages/libphonenumber_plugin/js/stringbuffer.js"></script>
<script>
var serviceWorkerVersion = null;
var scriptLoaded = false;

// 初始化Flutter应用,强制使用CanvasKit
async function initFlutterApp() {
  if (scriptLoaded) return;
  scriptLoaded = true;
  
  await _flutter.loader.loadEntrypoint({
    serviceWorker: {
      serviceWorkerVersion: serviceWorkerVersion,
    },
    onEntrypointLoaded: async function(engineInitializer) {
      let engineOptions = new _flutter.JsFlutterConfiguration();
      engineOptions.renderer = "canvaskit";
      let appRunner = await engineInitializer.initializeEngine(engineOptions);
      await appRunner.runApp();
    }
  });
}

if ('serviceWorker' in navigator) {
  window.addEventListener('load', function () {
    var serviceWorkerUrl = 'flutter_service_worker.js?v=' + serviceWorkerVersion;
    navigator.serviceWorker.register(serviceWorkerUrl)
      .then((reg) => {
        function waitForActivation(serviceWorker) {
          serviceWorker.addEventListener('statechange', () => {
            if (serviceWorker.state == 'activated') {
              console.log('Installed new service worker.');
              initFlutterApp();
            }
          });
        }
        if (!reg.active && (reg.installing || reg.waiting)) {
          waitForActivation(reg.installing || reg.waiting);
        } else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
          console.log('New service worker available.');
          reg.update();
          waitForActivation(reg.installing);
        } else {
          console.log('Loading app from service worker.');
          initFlutterApp();
        }
      });

    setTimeout(() => {
      if (!scriptLoaded) {
        console.warn('Failed to load app from service worker. Falling back to direct init.');
        initFlutterApp();
      }
    }, 4000);
  });
} else {
  // 不支持Service Worker时直接初始化
  window.addEventListener('load', initFlutterApp);
}
</script>
</body>
</html>

关键修改点:

  • 移除嵌套的load事件监听,直接在Service Worker激活后调用初始化函数
  • 弃用手动创建script标签的方式,改用Flutter官方flutter.js加载流程
  • 确保_flutter.loader在页面资源就绪后正确执行

CI/CD构建配置确认

如果要在构建阶段强制启用CanvasKit,发布命令需添加参数:

flutter build web --release --dart-define=FLUTTER_WEB_USE_SKIA=true

添加该参数后,构建产物默认使用CanvasKit,无需修改index.html即可生效。

内容的提问来源于stack exchange,提问作者JAgüero

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 06:27:59