前言
svg中的id,在整个node tree中不允许重复,不允许空格符号,不允许为空。
svg中的defs,W3规定,不会阻止其他元素来关联当前元素的defs的内容,因此全局node tree下的defs是公用的。
问题
在一个dom上下文中如果使用了多个相同的svg,比如一个tab组件的各个页面中,都用到了一个空数据的svg图片,该图片内部有定义defs和id的关联引用到一些填充或者渐变效果。当切换第一个tab之后的tab后,该svg图片会显示异常。
原因
第一个tab的svg一般会在dom的最上层位置,切换其他tab时,第一个tab可能会隐藏,比如css的display: none
(当然,如果直接不渲染,比如vif,则判定第二个svg的状态)。该tab下的svg也会被隐藏。此时其他tab的svg引用的defs会是第一个svg下的,此时的效果显示状态也会以第一个svg的显示状态来决定,也就是隐藏。这样图片所有引用的渐变或者填充效果都无法显示,导致了异常。
解决方案
-
使用不同的svg图片
-
给svg的各个id添加动态定义的效果,比如用vue包装svg成组件后,动态生成id数组,保证每一个svg被使用时都产生新的id内容。
-
既然svg通过一个dom上下文来确定当前所有svg的信息,那么将不同的svg放入不同的dom上下文中就好了,比如通过
iframe
或object
标签包装一层。 -
一般图标组件会有一种通过vue组件化的方式来调用。使用时直接以vue组件方式引入。
当从svg——>Vue Component时,可以在转化时,通过vue的特性,匹配替换svg内容中有通过url+id的方式引入公共元素的地方,一般有mask等。通过正则匹配,增加时间戳替换其id值和url内的内容,使用vue的组件props方式达到一个svg-vue组件内的id唯一的效果。
let content = svgData.inner // svg内所有元素形成的字符串
// 匹配svg中所有id="任意字符"的内容
const reg = /id="(.*?)"/g
const matches = content.match(reg)
const id2IdTimestampMap = new Map()
if (matches) {
matches.forEach((match) => {
// 匹配id="任意字符"中的任意字符
const id = match.match(/id="(.*?)"/)?.[1]
if (id) {
const replaceIdContent = `${id}`
// 替换svg中所有id="任意字符"的内容为id="任意字符+时间戳"
content = content.replace(
match,
`:id="'${replaceIdContent}' + timeStamp"`
)
id2IdTimestampMap.set(id, replaceIdContent)
}
})
}
// 匹配svg中所有\w+="url(#任意字符)"中的任意字符,同时替换成:\w+="`url(#${任意字符})`"
const reg2 = /(\w+)="url\(#(.*?)\)"/g
const matches2 = content.match(reg2)
if (matches2) {
matches2.forEach((match) => {
// 匹配\w+="url(#任意字符)"中的任意字符
const matches = match.match(/(\w+)="url\(#(.*?)\)"/)
const id = matches?.[2]
const attrName = matches?.[1]
if (id) {
const replaceIdContent = id2IdTimestampMap.get(id)
// 替换svg中所有\w+="url(#任意字符)"的内容为\w+="`url(#${任意字符})`"
content = content.replace(
match,
`:${attrName}="'url(#${replaceIdContent}'+timeStamp+')'"`
)
}
})
}