SVG 在 image 标签中的动态修改技巧

tag: Web; JavaScript; SVG; DOM; 动画 SVG

最近在项目中遇到了「带动画 SVG 图标」与 「image」标签结合使用的场景,使用过程中发现水还是有点深,因此整理出来,供有相似场景的童鞋以参考。

问题背景

我们这里有一个的带动画 SVG 文件

rings.gif

这是一个水波纹效果的 SVG,动画时长是固定的,但是我们希望不同的标记动画的播放时长可以略有不同,进而产生错落交错的感觉。期待效果如下:

rings_diff_ani.gif

其中控制动画时长的属性是写死在 SVG 文件中的(animate 标签的 dur 属性)。SVG 部分内容如下:

<circle cx="22" cy="22" r="6" stroke-opacity="0">
    <animate attributeName="r"
         begin="1.5s" dur="3s"
         values="6;22"
         calcMode="linear"
         repeatCount="indefinite" />
    <animate attributeName="stroke-opacity"
         begin="1.5s" dur="3s"
         values="1;0" calcMode="linear"
         repeatCount="indefinite" />
</circle>

另外受限于组件要求,仅能使用 image标签进行加载 SVG。因此只能从 image.src 属性作为突破入口。

解题思路

先确定基本思路:在 SVG 被插入到 image 标签前,修改 SVG 文件中动画 animate 标签的属性 dur,以达到修改动画时间的目的。

那么我们先来看下 image 加载 SVG 文件几种的方式。

1. image 的 src 直接指定文件路径

<img src="./assets/rings.svg">

这种方式加载出来的 SVG 内容没法被 JS 获取到,更不要提修改属性,因此该方案 放弃

2. 直接加载 base64 或者 Blob URL 字符串

这种方式中,依靠的是 image 标签支持 base64 和 Blob 类型 URL 的特性。

考虑可以先将 SVG 文件转换为对应的字符串,然后通过正则表达式将动画属性修改,然后传入 src 中来实现。不过这种方式需要编写复杂的正则表达式,并且字符串形式的 SVG 内容可读性较差,因此这种方式也 不是最优的

如果可以先以 dom 形式修改 SVG 的动画属性,再将 dom 转换为字符串,最后再将字符串转换为 base64 或者 Blob 类型,就可以实现以上的需求了。

看起来这个思路靠谱,那么就按照这个方向继续探索。

处理过程

1. 获取 dom

获取 SVG 的方式有很多,但获取方式不在本文重点,故只给出原生 JS 实现方式。

const xhr = new XMLHttpRequest();
xhr.addEventListener('load', () => {
  const resXML = xhr.responseXML;
  const svgDom = resXML.documentElement.cloneNode(true);
});
xhr.open('GET', './rings.svg');
xhr.send();

2. 修改 dom

上文获取的 svgDom 就是可操作的 dom 节点,接下来和操作 dom 一样来操作它。

// 获取 svg 中的 animate 标签,使用 setAttribute 进行修改 dur、begin 等属性,以下代码仅为示例
// 获取 animation 节点
const ani = svgDom.children[0];
// 修改节点上的动画时长 dur 属性
ani.setAttribute('dur', Math.random() + 2 + 's');

3. 转换 domString

接来下便需要将 dom 转换成字符串:通过 XMLSerializer 将 XML 转换成 String 类型。

const svgStr = new XMLSerializer().serializeToString(svgDom);

4. 转换 URL

但是上面的 svgStr 是没法直接传给 image.src 的,需要将其转换为 image 可以解析的 base64 或者 Blob URL 类型。

方案 A:

这里我选择了 Blob 类型进行转换,先用 new Blob([svgStr]) 转换成 Blob 类型,再通过 URL.createObjectURL(blob) 方法将字符串转换成 Blob 类型 URL 传入。

const blob = new Blob([svgStr], {
    type: 'image/svg+xml'
});
const blobStr = URL.createObjectURL(blob);
const template = `<img src="${blobStr}">`;
// 最后插入模板
方案 B:

当然除了使用 Blob 数据类型外,我们也可以使用window.btoa()方法将 svgStr 转换为 base64 类型传入。

const base64 = window.btoa(svgStr);
const template = `<img src="data:image/svg+xml;base64,${base64}">`
// 最后插入模板

结论

通过「读入 SVG -> 修改属性 -> 转换 URL -> 传入 image」 这样一个流程,最终实现了我们的预期。

以上思路希望对有类似场景的同学们有所启发。 最后强调的是使用环境为 Chrome 浏览器,其他浏览器未做测试。

参考资料

XMLHttpRequest
SVG Animate
XMLSerializer
Blob
URL.createObjectURL()
Using object URLs to display images
window.btoa()

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,161评论 1 32
  • 点击查看原文 Web SDK 开发手册 SDK 概述 网易云信 SDK 为 Web 应用提供一个完善的 IM 系统...
    layjoy阅读 13,996评论 0 15
  • 唐执阅读 188评论 0 0
  • 2017年是大学生涯的第二年,我19岁,从干事到部长,从一个人到一群人,上半年在胡思乱想,下半年则是投身圆梦计划,...
    建行小伙阅读 382评论 0 1
  • 时间抵不过一碗茶凉 爱人熬不过一段青春 终将是梦醒时 记忆里的旧地 沦为悲伤之所 愿风听过便带走伤 愿雨落下便携走...
    云云云林阅读 188评论 0 1