Safari 15环境下Canvas toDataURL将SVG转图片时输出异常
Safari 15 SVG转PNG异常解决方案
问题根因
你遇到的兼容性问题核心来自两个Safari的布局特性差异:
- 未显式设置
width/height属性、仅靠viewBox和百分比样式定义尺寸的SVG,加载到<img>标签后,Safari计算的clientWidth/clientHeight和Chrome逻辑不一致,大概率会拿到0或者不符合预期的数值,直接用于设置canvas宽高就会导致绘制内容截断/空白。 - 临时创建的SVG加载用
<img>插入到DOM后,会受页面全局样式影响,进一步放大不同浏览器的尺寸计算差异。
修复方案
直接解析SVG的viewBox属性获取原始宽高,完全不依赖DOM布局计算的尺寸值,同时避免将临时元素插入DOM,从根源消除兼容性差异。
修复后的代码如下:
const base64Image = 'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjkyLjk3NzUyODA4OTg4NzY0IDEyOSAzNzQuMDQ0OTQzODIwMjI0NyAxMTAiIGNsYXNzPSJQU1BERktpdC04czFzYmM1MTN0MzRxMzg4OTViNGJoM2p2biIgZm9jdXNhYmxlPSJmYWxzZSIgZGF0YS10ZXN0aWQ9Imluay1zdmciIHN0eWxlPSJsZWZ0OiAwcHg7IHRvcDogMHB4OyB3aWR0aDogMTAwJTsgaGVpZ2h0OiAxMDAlOyBvcGFjaXR5OiAxOyBvdmVyZmxvdzogaGlkZGVuOyI+PHJlY3QgeD0iOTcuOTc3NTI4MDg5ODg3NjQiIHk9IjEzNCIgd2lkdGg9IjM2NC4wNDQ5NDM4MjAyMjQ3IiBoZWlnaHQ9IjEwMCIgY2xhc3M9IlBTUERGS2l0LTc4NzFyZmh5eGI3cGRta3MxbjN4N2F2dnRhIiBzdHlsZT0iZmlsbDogdHJhbnNwYXJlbnQ7Ii8+PGcgc3R5bGU9ImZpbGw6IHRyYW5zcGFyZW50OyI+PGc+PHBhdGggY2xhc3M9IlBTUERGS2l0LTQ3cjV6eHB0eWtoanFwenFkdWU5YWNwaHR6IFBTUERGS2l0LTQ0OG5jcjVtdWhycGM4cDh5dmFkeTUxZW1jIFBTUERGS2l0LVNtb290aC1MaW5lcyIgZD0iTSAyMTIuNzYzODUwODAyMDQ4MiwxNjYuMDQwNTk0NDE4MjY3NDYgQyAxODMuNiwxNjAuNyAxNTQuNSwxNTUuNCAxMzUuNiwxNTIuMyBDIDExNi43LDE0OS4xIDEwNy45LDE0OC4yIDEwNC4wLDE0OS45IEMgMTAwLjIsMTUxLjUgMTAxLjEsMTU1LjkgMTAzLjMsMTYwLjcgQyAxMDUuNSwxNjUuNiAxMDguOSwxNzAuOSAxMTYuNCwxNzYuMiBDIDEyMy45LDE4MS41IDEzNS42LDE4Ni44IDE0Mi45LDE4Ny4xIEMgMTUwLjIsMTg3LjMgMTUzLjEsMTgyLjUgMTU1LjUsMTc3LjYgQyAxNTcuOSwxNzIuOCAxNTkuOSwxNjguMCAxNjEuMSwxNjUuMyBDIDE2Mi4zLDE2Mi43IDE2Mi44LDE2Mi4yIDE2NS43LDE2NS44IEMgMTY4LjYsMTY5LjQgMTczLjksMTc3LjIgMTgwLjcsMTg1LjQgQyAxODcuNSwxOTMuNiAxOTUuOCwyMDIuMyAyMDIuMSwyMDcuNiBDIDIwOC40LDIxMi45IDIxMi44LDIxNC45IDIxOC4zLDIxNS42IEMgMjIzLjksMjE2LjMgMjMwLjcsMjE1LjggMjM1LjYsMjEyLjkgQyAyNDAuNCwyMTAuMCAyNDMuMywyMDQuNyAyNDYuMywxOTcuMiBDIDI0OS4yLDE4OS43IDI1Mi4xLDE4MC4xIDI1My41LDE3NS4wIEMgMjU1LjAsMTY5LjkgMjU1LjAsMTY5LjQgMjU1LjIsMTY5LjIgQyAyNTUuNSwxNjguOSAyNTYuMCwxNjguOSAyNTYuOSwxNjkuNCBDIDI1Ny45LDE2OS45IDI1OS40LDE3MC45IDI2MS41LDE7MS44IEMgMjYzLjcsMTcyLjggMjY2LjYsMTczLjggMjY5LjgsMTc0LjMgQyAyNzIuOSwxNzQuNyAyNzYuMywxNzQuNyAyNzkuMywxNzQuMCBDIDI4Mi4yLDE3My4zIDI4NC42LDE3MS44IDI4Ni41LDE2OS45IEMgMjg4LjUsMTY4LjAgMjg5LjksMTY1LjYgMjkwLjksMTYxLjIgQyAyOTEuOSwxNTYuOSAyOTIuNCwxNTAuNiAyOTAuMiwxNDYuMCBDIDI4OC4wLDE0MS40IDI4My4xLDEzOC41IDI3Ny44LDEzNy4wIEMgMjcyLjUsMTM1LjYgMjY2LjYsMTM1LjYgMjYzLjUsMTM1LjYgQyAyNjAuMywxMzUuNiAyNTkuOCwxMzUuNiAyNTkuNCwxMzYuMSBDIDI1OC45LDEzNi42IDI1OC40LDEzNy41IDI2MS4zLDE0MC45IEMgMjY0LjIsMTQ0LjMgMjcwLjUsMTUwLjEgMjgzLjksMTU2LjEgQyAyOTcuMiwxNjIuMiAzMTcuNiwxNjguNSAzMjkuNSwxNzEuNiBDIDM0MS40LDE3NC43IDM0NC44LDE3NC43IDM1MC44LDE3NC4zIEMgMzU2LjksMTczLjggMzY1LjcsMTcyLjggMzcwLjMsMTcyLjEgQyAzNzQuOSwxNzEuNCAzNzUuNCwxNzAuOSAzNzUuNiwxNzIuMyBDIDM3NS44LDE3My44IDM3NS44LDE3Ny4yIDM3NS44LDE4Mi43IEMgMzc1LjgsMTg4LjMgMzc1LjgsMTk2LjAgMzc1LjYsMjAzLjUgQyAzNzUuNCwyMTEuMCAzNzQuOSwyMTguMiAzNzQuOSwyMjMuMSBDIDM3NC45LDIyNy45IDM3NS40LDIzMC4zIDM3OS43LDIzMS41IEMgMzg0LjEsMjMyLjcgMzkyLjMsMjMyLjcgNDAwLjYsMjMyLjIgQyA0MDguOCwyMzEuOCA0MTcuMSwyMzAuOCA0MjUuNiwyMjkuNiBDIDQzNC4xLDIyOC40IDQ0Mi44LDIyNi45IDQ0OC42LDIyNi4wIEMgNDU0LjUsMjI1LjAgNDU3LjQsMjI0LjUgNDYwLjMsMjI0LjAiIGRhdGEtdGVzdGlkPSJpbmstcGF0aCIgc3R5bGU9InN0cm9rZTogcmdiKDAsIDAsIDApOyBzdHJva2Utd2lkdGg6IDRweDsgcG9pbnRlci1ldmVudHM6IG5vbmU7Ii8+PHBhdGggY2xhc3M9IlBTUERGS2l0LTQ3cjV6eHB0eWtoanFwenFkdWU5YWNwaHR6IFBTUERGS2l0LTQ0OG5jcjVtdWhycGM4cDh5dmFkeTUxZW1jIFBTUERGS2l0LVNtb290aC1MaW5lcyIgc3Ryb2tlPSJ0cmFuc3BhcmVudCIgZD0iTSAyMTIuNzYzODUwODAyMDQ4MiwxNjYuMDQwNTk0NDE4MjY3NDYgQyAxODMuNiwxNjAuNyAxNTQuNSwxNTUuNCAxMzUuNiwxNTIuMyBDIDExNi43LDE0OS4xIDEwNy45LDE0OC4yIDEwNC4wLDE0OS45IEMgMTAwLjIsMTUxLjUgMTAxLjEsMTU1LjkgMTAzLjMsMTYwLjcgQyAxMDUuNSwxNjUuNiAxMDguOSwxNzAuOSAxMTYuNCwxNzYuMiBDIDEyMy45LDE4MS41IDEzNS42LDE4Ni44IDE0Mi45LDE4Ny4xIEMgMTUwLjIsMTg3LjMgMTUzLjEsMTgyLjUgMTU1LjUsMTc3LjYgQyAxNTcuOSwxNzIuOCAxNTkuOSwxNjguMCAxNjEuMSwxNjUuMyBDIDE2Mi4zLDE2Mi43IDE2Mi44LDE2Mi4yIDE2NS43LDE2NS44IEMgMTY4LjYsMTY5LjQgMTczLjksMTc3LjIgMTgwLjcsMTg1LjQgQyAxODcuNSwxOTMuNiAxOTUuOCwyMDIuMyAyMDIuMSwyMDcuNiBDIDIwOC40LDIxMi45IDIxMi44LDIxNC45IDIxOC4zLDIxNS42IEMgMjIzLjksMjE2LjMgMjMwLjcsMjE1LjggMjM1LjYsMjEyLjkgQyAyNDAuNCwyMTAuMCAyNDMuMywyMDQuNyAyNDYuMywxOTcuMiBDIDI0OS4yLDE4OS43IDI1Mi4xLDE4MC4xIDI1My41LDE3NS4wIEMgMjU1LjAsMTY5LjkgMjU1LjAsMTY5LjQgMjU1LjIsMTY5LjIgQyAyNTUuNSwxNjguOSAyNTYuMCwxNjguOSAyNTYuOSwxNjkuNCBDIDI1Ny45LDE2OS45IDI1OS40LDE3MC45IDI2MS41LDE3MS44IEMgMjYzLjcsMTcyLjggMjY2LjYsMTczLjggMjY5LjgsMTc0LjMgQyAyNzIuOSwxNzQuNyAyNzYuMywxNzQuNyAyNzkuMywxNzQuMCBDIDI4Mi4yLDE3My4zIDI4NC42LDE7MS44IDI4Ni41LDE2OS45IEMgMjg4LjUsMTY4LjAgMjg5LjksMTY1LjYgMjkwLjksMTYxLjIgQyAyOTEuOSwxNTYuOSAyOTIuNCwxNTAuNiAyOTAuMiwxNDYuMCBDIDI4OC4wLDE0MS40IDI4My4xLDEzOC41IDI3Ny44LDEzNy4wIEMgMjcyLjUsMTM1LjYgMjY2LjYsMTM1LjYgMjYzLjUsMTM1LjYgQyAyNjAuMywxMzUuNiAyNTkuOCwxMzUuNiAyNTkuNCwxMzYuMSBDIDI1OC45LDEzNi42IDI1OC40LDEzNy41IDI2MS4zLDE0MC45IEMgMjY0LjIsMTQ0LjMgMjcwLjUsMTUwLjEgMjgzLjksMTU2LjEgQyAyOTcuMiwxNjIuMiAzMTcuNiwxNjguNSAzMjkuNSwxNzEuNiBDIDM0MS40LDE3NC43IDM0NC44LDE3NC43IDM1MC44LDE3NC4zIEMgMzU2LjksMTczLjggMzY1LjcsMTcyLjggMzcwLjMsMTcyLjEgQyAzNzQuOSwxNzEuNCAzNzUuNCwxNzAuOSAzNzUuNiwxNzIuMyBDIDM3NS44LDE3My44IDM3NS44LDE3Ny4yIDM3NS44LDE4Mi43IEMgMzc1LjgsMTg4LjMgMzc1LjgsMTk2LjAgMzc1LjYsMjAzLjUgQyAzNzUuNCwyMTEuMCAzNzQuOSwyMTguMiAzNzQuOSwyMjMuMSBDIDM3NC45LDIyNy45IDM3NS40LDIzMC4zIDM3OS43LDIzMS41IEMgMzg0LjEsMjMyLjcgMzkyLjMsMjMyLjcgNDAwLjYsMjMyLjIgQyA0MDguOCwyMzEuOCA0MTcuMSwyMzAuOCA0MjUuNiwyMjkuNiBDIDQzNC4xLDIyOC40IDQ0Mi44LDIyNi45IDQ0OC42LDIyNi4wIEMgNDU0LjUsMjI1LjAgNDU3LjQsMjI0LjUgNDYwLjMsMjI0LjAiIGRhdGEtdGVzdGlkPSJjbGlja2FibGUtcGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogNXB4OyIvPjwvZz48L2c+PC9zdmc+'; // 解析SVG的viewBox获取原始宽高 function getSvgViewBoxSize(svgStr) { const parser = new DOMParser(); const svgDoc = parser.parseFromString(svgStr, 'image/svg+xml'); const svgEl = svgDoc.documentElement; const viewBox = svgEl.viewBox.baseVal; return { width: viewBox.width, height: viewBox.height }; } const svg = window.atob(base64Image); const svgSize = getSvgViewBoxSize(svg); svgToPng(svg, svgSize, (imgData) => { const pngImage = document.createElement('img'); document.body.appendChild(pngImage); pngImage.src = imgData; }); function svgToPng(svg, size, callback) { const url = getSvgUrl(svg); svgUrlToPng(url, size, (imgData) => { callback(imgData); URL.revokeObjectURL(url); }); } function getSvgUrl(svg) { return URL.createObjectURL(new Blob([svg], { type: 'image/svg+xml' })); } function svgUrlToPng(svgUrl, size, callback) { const svgImage = new Image(); // 不要插入到DOM,避免样式干扰 svgImage.onload = function () { const canvas = document.createElement('canvas'); // 直接用viewBox的宽高,不依赖clientWidth/clientHeight canvas.width = size.width; canvas.height = size.height; const canvasCtx = canvas.getContext('2d'); canvasCtx.drawImage(svgImage, 0, 0, size.width, size.height); const imgData = canvas.toDataURL('image/png'); callback(imgData); // 清理临时img对象 svgImage.src = ''; }; svgImage.src = svgUrl; }
额外注意点
你提供的SVG代码中第二个path的d属性存在语法错误,包含无效字符,建议先修正SVG源文件的语法问题,避免不同浏览器的容错逻辑差异导致渲染结果不一致。
内容的提问来源于stack exchange,提问作者pritesh
相关产品推荐
相关产品推荐

