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

Flutter Web同时启用Html与CanvasKit渲染的方案咨询

问题:Flutter Web 同时兼容 Google Maps 文本标记与跨域图片加载

我的项目多数功能依赖 --web-renderer html 渲染,但Google Maps的自定义文本标记功能仅在 --web-renderer canvaskit 下能正常显示。尝试使用 --web-renderer auto 无法解决核心矛盾:

  • CanvasKit渲染:地图文本标记加载正常,但来自多提供商的图片因跨域/加载问题无法显示,即便服务端配置了CORS也无效,只有Html渲染能正常加载这些图片;
  • Html渲染:图片加载正常,但自定义文本标记完全无法显示;
  • 试过用image_network替代CachedNetworkImage,但加载5-10张图就会卡顿,无法满足需求。

希望找到能同时让地图文本标记和图片都正常渲染的方案。


创建自定义地图标记的代码

Future<BitmapDescriptor> createCustomMarkerBitmap(String title,
    {required TextStyle textStyle,
    Color backgroundColor = Colors.blueAccent}) async {
  TextSpan span = TextSpan(
    style: textStyle,
    text: title,
  );
  TextPainter painter = TextPainter(
    text: span,
    textAlign: TextAlign.center,
    textDirection: ui.TextDirection.ltr,
  );
  painter.text = TextSpan(
    text: title.toString(),
    style: textStyle,
  );
  ui.PictureRecorder pictureRecorder = ui.PictureRecorder();
  Canvas canvas = Canvas(pictureRecorder);
  painter.layout();
  painter.paint(canvas, const Offset(20.0, 10.0));
  int textWidth = painter.width.toInt();
  int textHeight = painter.height.toInt();
  canvas.drawRRect(
      RRect.fromLTRBAndCorners(0, 0, textWidth + 40, textHeight + 20,
          bottomLeft: const Radius.circular(10),
          bottomRight: const Radius.circular(10),
          topLeft: const Radius.circular(10),
          topRight: const Radius.circular(10)),
      Paint()..color = backgroundColor);
  var arrowPath = Path();
  arrowPath.moveTo((textWidth + 40) / 2 - 15, textHeight + 20);
  arrowPath.lineTo((textWidth + 40) / 2, textHeight + 40);
  arrowPath.lineTo((textWidth + 40) / 2 + 15, textHeight + 20);
  arrowPath.close();
  canvas.drawPath(arrowPath, Paint()..color = backgroundColor);
  painter.layout();
  painter.paint(canvas, const Offset(20.0, 10.0));
  ui.Picture p = pictureRecorder.endRecording();
  ByteData? pngBytes = await (await p.toImage(
          painter.width.toInt() + 40, painter.height.toInt() + 50))
      .toByteData(format: ui.ImageByteFormat.png);
  Uint8List data = Uint8List.view(pngBytes!.buffer);
  return BitmapDescriptor.fromBytes(data);
}

图片加载代码(CanvasKit下失效)

CachedNetworkImage(
        placeholder: (context, imageUrl) => SizedBox(
          width: imageWidth,
          height: imageHeight,
          child: Container(
            width: imageWidth,
            height: imageHeight,
            alignment: Alignment.center,
            child: CircularProgressIndicator(
              valueColor: AlwaysStoppedAnimation<Color>(
                  AppColors.termAndPolicyColor),
            ),
          ),
        ),
        errorWidget: (context, imageUrl, error) => Center(
          child: Icon(
            Icons.image,
            size:
                imageHeight, // Responsive.isDesktop(context) ? 200.sp : 100.sp,
            color: AppColors.orangeTextColor.withOpacity(0.8),
          ),
        ),
        imageBuilder: (context, imageProvider) => Container(
          width: imageWidth,
          height: imageHeight,
          decoration: BoxDecoration(
            shape: boxShape,
            image: DecorationImage(image: imageProvider, fit: BoxFit.cover),
          ),
        ),
        imageUrl: imageUrl,
      )

解决方案

方案1:为地图组件单独启用CanvasKit渲染

Flutter Web支持为单个Widget指定渲染模式,可将Google Maps组件单独用CanvasKit渲染,页面其余部分保持Html渲染,规避两者的兼容性冲突:

  1. 构建独立的CanvasKit渲染地图页面,通过iframe嵌入到主Html渲染页面中;
  2. 主页面与iframe通过postMessage通信,传递标记数据、坐标等信息,由iframe内的地图负责渲染文本标记;
  3. 主页面的图片仍用Html渲染,不受CanvasKit的CORS限制。

方案2:修改标记生成逻辑,适配Html渲染

当前标记生成依赖CanvasKit专属的PictureRecorder/CanvasAPI,在Html渲染下兼容性差。可改用HTML DOM生成标记再转图片:

  1. 用dart:html包创建包含文本、背景、箭头的DOM元素;
  2. 将DOM元素绘制到HTML Canvas,转为base64格式的图片;
  3. 把base64转为Uint8List,生成Html渲染兼容的BitmapDescriptor。

示例代码:

import 'dart:html' as html;
import 'dart:typed_data';
import 'dart:convert';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:flutter/material.dart';

Future<BitmapDescriptor> createHtmlCompatibleMarker(String title, TextStyle textStyle, Color backgroundColor) async {
  // 创建包含文本的容器
  final textContainer = html.DivElement()
    ..style.padding = '10px 20px'
    ..style.backgroundColor = '#${backgroundColor.value.toRadixString(16).substring(2)}'
    ..style.borderRadius = '10px'
    ..style.color = '#${textStyle.color!.value.toRadixString(16).substring(2)}'
    ..style.fontSize = '${textStyle.fontSize?.value}px'
    ..style.fontWeight = textStyle.fontWeight?.toString().split('.').last
    ..text = title;

  // 创建箭头元素
  final arrow = html.DivElement()
    ..style.width = '0'
    ..style.height = '0'
    ..style.borderLeft = '15px solid transparent'
    ..style.borderRight = '15px solid transparent'
    ..style.borderTop = '20px solid #${backgroundColor.value.toRadixString(16).substring(2)}'
    ..style.margin = '0 auto';

  // 组合容器
  final markerContainer = html.DivElement()
    ..append(textContainer)
    ..append(arrow);

  // 渲染到Canvas
  final canvas = html.CanvasElement();
  final renderer = canvas.getContext('2d') as html.CanvasRenderingContext2D;
  canvas.width = markerContainer.offsetWidth;
  canvas.height = markerContainer.offsetHeight;

  // 将DOM绘制到Canvas
  await html.window.canvasKit!.domCanvasToCanvas(markerContainer, canvas);

  // 转为Uint8List并生成BitmapDescriptor
  final dataUrl = canvas.toDataUrl('image/png');
  final base64Str = dataUrl.split(',').last;
  final bytes = base64Decode(base64Str);

  return BitmapDescriptor.fromBytes(bytes);
}

方案3:解决CanvasKit下的图片跨域问题

若坚持全局用CanvasKit渲染,可通过以下方式解决图片加载问题:

  1. 搭建图片代理服务器,转发第三方图片并添加CORS响应头,让CanvasKit能正常加载;
  2. 针对不同图片提供商,在CachedNetworkImage的httpHeaders中配置自定义请求头(部分提供商支持通过头信息允许跨域);
  3. 预加载图片到内存缓存,转为MemoryImage后再使用,绕开CanvasKit的跨域检查。

内容的提问来源于stack exchange,提问作者Ankit Miyan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 04:25:04