backgroundImage 引起的问题
<template>
<div class="page" :style="'backgroundImage: url(' + url + ');padding: 50px'"></div>
</template>
<script>
export default {
data() {
return {
url: 'https://v2.cn.vuejs.org/images/logo.svg'
}
}
}
</script>
以上这段代码在 Vue2 中表现如预期,背景图片正常渲染,但在 Vue3 中就出问题了,背景图片没显示,style 里并没有 background-image 属性。
改成如下形式 Vue2 和 Vue3 都能正常渲染:
<div class="page" :style="'background-image: url(' + url + ');padding: 50px'"></div>
当然官方文档没有推荐使用字符串语法,规范应该采用对象语法。
<div class="page" :style="{ backgroundImage: 'url(' + url + ')', padding: '50px' }"></div>
但为什么 <div class="page" :style="'backgroundImage: url(' + url + ');padding: 50px'"></div> 在 Vue2 能正常设置 backgroundImage,而 Vue3 就不行了呢?Vue2 和 Vue3 在处理 style 内联样式的字符串值有什么不同呢?下面就扒一扒源码一探究竟!
Vue2 为什么可以
先来看看 Vue2 是怎么处理的。核心代码如下:
src/platforms/web/util/style.ts
摘取重点代码,打印点日志看看。
解析 style 字符串值的逻辑:
export const parseStyleText = cached(function (cssText) {
console.log(33333, cssText) // 打个日志看看
const res = {}
const listDelimiter = /;(?![^(]*\))/g
const propertyDelimiter = /:(.+)/
cssText.split(listDelimiter).forEach(function (item) {
if (item) {
const tmp = item.split(propertyDelimiter)
tmp.length > 1 && (res[tmp[0].trim()] = tmp[1].trim())
}
})
return res
})
// normalize possible array / string values into Object
export function normalizeStyleBinding(bindingStyle: any): Record<string, any> {
if (Array.isArray(bindingStyle)) {
return toObject(bindingStyle)
}
if (typeof bindingStyle === 'string') {
return parseStyleText(bindingStyle)
}
return bindingStyle
}
src/platforms/web/runtime/modules/style.ts
设置 style 属性的逻辑:
const setProp = (el, name, val) => {
console.log(22222, el, name, val) // 打个日志看看
/* istanbul ignore if */
if (cssVarRE.test(name)) {
el.style.setProperty(name, val)
} else if (importantRE.test(val)) {
el.style.setProperty(
hyphenate(name),
val.replace(importantRE, ''),
'important'
)
} else {
const normalizedName = normalize(name)
console.log(66666, normalizedName, val) // 打个日志看看
if (Array.isArray(val)) {
// Support values array created by autoprefixer, e.g.
// {display: ["-webkit-box", "-ms-flexbox", "flex"]}
// Set them one by one, and the browser will only set those it can recognize
for (let i = 0, len = val.length; i < len; i++) {
el.style[normalizedName!] = val[i]
}
} else {
el.style[normalizedName!] = val
}
}
}
const vendorNames = ['Webkit', 'Moz', 'ms']
let emptyStyle
const normalize = cached(function (prop) {
emptyStyle = emptyStyle || document.createElement('div').style
prop = camelize(prop)
if (prop !== 'filter' && prop in emptyStyle) {
return prop
}
const capName = prop.charAt(0).toUpperCase() + prop.slice(1)
for (let i = 0; i < vendorNames.length; i++) {
const name = vendorNames[i] + capName
if (name in emptyStyle) {
return name
}
}
})
- background-image
- backgroundImage
从源码调试打印的日志可以看出:Vue2 会把 style 的字符串形式值解析拆分出单独的属性键值对,比如
-
:style="'background-image: url(' + url + ');padding: 50px''"解析成{ background-image: 'url(https://v2.cn.vuejs.org/images/logo.svg)', padding: '50px' } -
:style="'backgroundImage: url(' + url + ');padding: 50px''"解析成{ backgroundImage: 'url(https://v2.cn.vuejs.org/images/logo.svg)', padding: '50px' }
normalize 方法是把短横线属性名转换成驼峰形式,所以两种形式最终都是执行:
el.style.backgroundImage = 'url(https://v2.cn.vuejs.org/images/logo.svg)'
el.style.padding = '50px'
这也就解释了为什么 Vue2 中 style="background-image: ..." 和 style="backgroundImage: ..." 都支持。
Vue3 为什么不行
再来看看 Vue3 是怎么处理的。核心代码如下:
packages/runtime-dom/src/modules/style.ts
摘取重点代码,打印点日志看看。
export function patchStyle(el: Element, prev: Style, next: Style): void {
console.log(123123, el, prev, next) // 打个日志看看
const style = (el as HTMLElement).style
const isCssString = isString(next)
let hasControlledDisplay = false
if (next && !isCssString) {
if (prev) {
if (!isString(prev)) {
for (const key in prev) {
if (next[key] == null) {
setStyle(style, key, '')
}
}
} else {
for (const prevStyle of prev.split(';')) {
const key = prevStyle.slice(0, prevStyle.indexOf(':')).trim()
if (next[key] == null) {
setStyle(style, key, '')
}
}
}
}
for (const key in next) {
if (key === 'display') {
hasControlledDisplay = true
}
setStyle(style, key, next[key])
}
} else {
if (isCssString) {
if (prev !== next) {
// #9821
const cssVarText = (style as any)[CSS_VAR_TEXT]
if (cssVarText) {
;(next as string) += ';' + cssVarText
}
style.cssText = next as string
hasControlledDisplay = displayRE.test(next)
}
} else if (prev) {
el.removeAttribute('style')
}
}
// indicates the element also has `v-show`.
if (vShowOriginalDisplay in el) {
// make v-show respect the current v-bind style display when shown
el[vShowOriginalDisplay] = hasControlledDisplay ? style.display : ''
// if v-show is in hidden state, v-show has higher priority
if ((el as VShowElement)[vShowHidden]) {
style.display = 'none'
}
}
}
- backgroundImage
- background-image
最后执行的是如下分支逻辑:
if (isCssString) {
if (prev !== next) {
// #9821
const cssVarText = (style as any)[CSS_VAR_TEXT]
if (cssVarText) {
;(next as string) += ';' + cssVarText
}
style.cssText = next as string
hasControlledDisplay = displayRE.test(next)
}
}
可以看出,Vue3 的处理是直接将 style 的字符串值赋值给属性 cssText:style.cssText = next
el.style.cssText = 'background-image: url(https://v2.cn.vuejs.org/images/logo.svg);padding: 50px'
el.style.cssText = 'backgroundImage: url(https://v2.cn.vuejs.org/images/logo.svg);padding: 50px'
显然在style的cssText里使用backgroundImage是无效的,这种驼峰类型的style属性名称都是无效的。
CSSStyleDeclaration: cssText property - MDN
The
cssTextproperty of theCSSStyleDeclarationinterface returns or sets the text of the element's inline style declaration only.
当然这也不能说是 Vue3 的bug,毕竟 style 的字符串值里使用 backgroundImage 本就不符合标准语法。
不过这也是 Vue2 项目升级 Vue3 潜在的一个坑。