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

dom-to-image在Safari中批量转换含canvas的DOM为JPEG失效求助

Safari下dom-to-image批量转换含Fabric Canvas的DOM元素生成空白图的解决方案

问题场景

我开发了一个WordPress插件,实现用户自定义T恤功能:用户上传图片后添加到Fabric Canvas,可调整大小和位置。在将包含T恤背景图和Canvas的div转为JPEG上传服务器时,Firefox、Chrome、Edge都正常,但Safari出现异常:单独转换正面或背面div时生成正常图片,同时转换正反两面会生成两张空白图。试过两次调用domtoimage.toJpeg()的临时方案无效,也试过html2Canvas,但它只能转换T恤背景图,无法识别Canvas中的用户上传内容。

极简复现代码

<div class="tabby-tab">
    <input type="radio" id="0" class="tab_ch" name="tabby-tabs">
    <label for="0">Front</label>
    <div class="tabby-content">
        <div class="full_img_view mx-auto" id="tshirt_div_0">
            <img src="src_of_front_image" class="front_img">
            <div class="drawing-area-front">                    
                <canvas id="tshirt_canvas"></canvas>
            </div>
        </div>
    </div>
</div>

<div class="tabby-tab">
    <input type="radio" id="1" class="tab_ch" name="tabby-tabs">
    <label for="1">Back</label>
    <div class="tabby-content">
        <div class="full_img_view mx-auto" id="tshirt_div_1">
            <img src="src_of_back_image" class="back_img">
            <div class="drawing-area-back">                 
                <canvas id="tshirt_canvas"></canvas>
            </div>
        </div>
    </div>
</div>

<br>

<button id="add_to_cart">Add to cart</button>

<script>
    jQuery(document).on('click', '#add_to_cart', function() {
        var total_canva = jQuery('.full_img_view');
        jQuery.each(total_canva, function(index, item) {
            var gradCanvas = canvasList[index];
            gradCanvas.discardActiveObject();
            gradCanvas.requestRenderAll();

            document.getElementById('canv_img_'+index).innerHTML = "";
            var options = {quality: 0.95, allowTaint: true, useCORS: true};
                
            setTimeout(function() {
                domtoimage.toJpeg(item, options).then(function(dataUrl) {
                    domtoimage.toJpeg(item, options).then(function(dataUrl2) {
                        var img = new Image();
                        img.src = dataUrl2;
                        img.style="display: none";
                        console.log(img);
                    });
                });
            },50);
        });
    });
</script>

解决方案

1. 改用串行处理避免异步冲突

Safari对Canvas异步渲染的资源竞争更敏感,并行调用dom-to-image会导致渲染空白。将批量操作改成串行Promise链式调用:

jQuery(document).on('click', '#add_to_cart', function() {
    var total_canva = jQuery('.full_img_view');
    let promiseChain = Promise.resolve();
    
    total_canva.each(function(index, item) {
        promiseChain = promiseChain.then(() => {
            var gradCanvas = canvasList[index];
            gradCanvas.discardActiveObject();
            gradCanvas.requestRenderAll();

            document.getElementById('canv_img_'+index).innerHTML = "";
            var options = {quality: 0.95, allowTaint: true, useCORS: true};
            
            return domtoimage.toJpeg(item, options).then(dataUrl => {
                var img = new Image();
                img.src = dataUrl;
                img.style="display: none";
                console.log(img);
            });
        });
    });
    
    promiseChain.catch(err => console.error('转换失败:', err));
});

2. 等待Fabric Canvas渲染完成后再转换

Fabric的requestRenderAll()是异步操作,Safari中可能未完成渲染就被dom-to-image捕获。利用afterRender事件确保渲染完成:

jQuery(document).on('click', '#add_to_cart', function() {
    var total_canva = jQuery('.full_img_view');
    let promiseChain = Promise.resolve();
    
    total_canva.each(function(index, item) {
        promiseChain = promiseChain.then(() => {
            return new Promise(resolve => {
                var gradCanvas = canvasList[index];
                gradCanvas.discardActiveObject();
                
                gradCanvas.once('afterRender', () => {
                    document.getElementById('canv_img_'+index).innerHTML = "";
                    var options = {quality: 0.95, allowTaint: true, useCORS: true};
                    
                    domtoimage.toJpeg(item, options).then(dataUrl => {
                        var img = new Image();
                        img.src = dataUrl;
                        img.style="display: none";
                        console.log(img);
                        resolve();
                    }).catch(err => {
                        console.error(err);
                        resolve();
                    });
                });
                gradCanvas.requestRenderAll();
            });
        });
    });
});

3. 用原生Canvas合成替代dom-to-image(最可靠)

直接将背景图和Fabric Canvas内容合成到新Canvas,避开第三方库的兼容性问题:

jQuery(document).on('click', '#add_to_cart', function() {
    var total_canva = jQuery('.full_img_view');
    
    total_canva.each(function(index, item) {
        var gradCanvas = canvasList[index];
        gradCanvas.discardActiveObject();
        gradCanvas.requestRenderAll();
        
        const bgImg = item.querySelector('img');
        const fabricCanvasEl = gradCanvas.getElement();
        
        // 创建合成Canvas
        const compositeCanvas = document.createElement('canvas');
        const ctx = compositeCanvas.getContext('2d');
        
        // 匹配背景图尺寸
        compositeCanvas.width = bgImg.width;
        compositeCanvas.height = bgImg.height;
        
        // 先画背景图,再画Canvas内容
        ctx.drawImage(bgImg, 0, 0);
        ctx.drawImage(fabricCanvasEl, 0, 0);
        
        // 转成JPEG
        const dataUrl = compositeCanvas.toDataURL('image/jpeg', 0.95);
        var img = new Image();
        img.src = dataUrl;
        img.style="display: none";
        console.log(img);
    });
});

4. 排查Safari隐私和CORS设置

  • 确保所有图片(背景图、用户上传图)的服务器返回正确的CORS响应头,Safari对跨域资源的限制更严格;
  • 检查Safari是否开启"阻止跨域跟踪"等隐私设置,这类设置可能影响图片加载和Canvas渲染。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 12:07:48