VUE&CSS原理
Vue 里的 <style scoped> 作用是:让当前组件的样式只作用于当前组件的 DOM,不影响全局。
scope原理
scoped 的作用
Vue 里的 <style scoped> 作用是:让当前组件的样式只作用于当前组件的 DOM,不影响全局。
实现方式并不是运行时隔离,而是 编译阶段给 DOM 和 CSS 选择器加特殊标记。
2. 基本例子
<template>
<div class="parent">
<p class="child">Hello</p>
</div>
</template>
<style scoped>
.parent {
color: red;
}
.child {
font-size: 20px;
}
</style>编译后的结果
Vue Loader 在编译模板时,会给组件根元素以及子元素都加上一个 特定的属性标记(比如 data-v-123abc):
<div class="parent" data-v-123abc>
<p class="child" data-v-123abc>Hello</p>
</div>样式编译后的 CSS
对应的 CSS 选择器也会被加上 [data-v-123abc],确保只匹配带这个属性的元素:
.parent[data-v-123abc] {
color: red;
}
.child[data-v-123abc] {
font-size: 20px;
}这样,哪怕全局也有 .child 类名,它也不会被这里的样式覆盖。
3. ::v-deep 的特殊处理
如果我们加一个子组件 <Child />,想影响它内部的 .title,直接写会失效:
<style scoped>
.title {
color: blue;
}
</style>编译后会变成:
.title[data-v-123abc] { color: blue; }子组件里的 DOM 不会有 data-v-123abc,所以样式无法命中。
使用 ::v-deep
<style scoped>
::v-deep(.title) {
color: blue;
}
</style>编译后会变成:
[data-v-123abc] .title {
color: blue;
}
它是一个 属性选择器 (attribute selector) + 普通类选择器的组合。
表示:选择所有在带有 data-v-123abc 的元素里面的,class 名为 title 的子元素。这样就能影响到子组件的 .title 了。(而且是精准的子元素!不是全局的!)
deep形式讲解
Vue 2.x 初期
早期有几种写法:
>>>
.parent >>> .child { color: red; }- 属于 CSS combinator hack,Vue-loader 在编译时识别并替换。
- 不过
>>>在某些 CSS 预处理器(如 Sass)里会报错。
/deep/
.parent /deep/ .child { color: red; }- 来自 Shadow DOM 的穿透选择器,虽然标准后来废弃了,但 Vue 借鉴了它。
- 编译时
/deep/会被替换成[data-v-xxxx]选择器,从而作用到子组件。
Vue 2.x 后期
出现了 ::v-deep(Vue-loader 15 之后加入),官方开始推荐这种写法:
::v-deep(.child) {
color: red;
}这时候,>>> 和 /deep/ 依旧可用,但逐渐被认为是 过渡方案。
Vue 3.x
官方 完全统一 样式穿透写法为 ::v-deep。
语法更灵活,有两种主要形式:
-
函数式
css.parent ::v-deep(.child) { color: red; }推荐写法,清晰直观。
-
包裹式
css::v-deep(.child) { color: red; }效果相当于把
.child应用到所有有scoped的元素下。
其他兼容形式
- Vue 3 在构建时依然兼容
/deep/和>>>,但是官方文档明确建议只用::v-deep。 - 因为
/deep/来源于废弃的草案,而>>>本身不是标准语法。
语法糖
在 Vue 的样式穿透 里,确实会看到有人写 :deep,有人写 ::v-deep,这不是笔误
1. ::v-deep(官方写法)
Vue 官方推荐的标准写法。
出现在 Vue Loader 15+(Vue 2 后期)以及 Vue 3。
语法类似伪元素,所以有两个冒号:
.parent ::v-deep(.child) {
color: red;
}2. :deep(语法糖 / 简写)
在 <style scoped lang="scss"> 或 <style scoped lang="less"> 时,
因为 ::v-deep 在 Sass/LESS 编译时容易被当成伪元素报错,所以 Vue Loader 提供了 替代语法:
.parent :deep(.child) {
color: red;
}编译后效果 和 ::v-deep 一样。
这是给预处理器用户的兼容方案。
安全区科普
什么时候需要适配状态栏
无法全屏的竖屏移动端h5项目,无需考虑状态栏以及刘海屏问题。因为无法全屏的移动端h5项目,通常都是使用手机浏览器打开,而手机浏览器没法全屏
刘海屏无需考虑状态栏,只需考虑刘海安全区高度。因为状态栏就在刘海区中。
非刘海屏(包含普通屏幕以及各种异形屏)需要考虑状态栏高度。
只有混合开发且支持web-view全屏的移动端h5项目,需要考虑适配状态栏以及刘海屏适配问题。
无法全屏,但支持横屏的移动端h5项目,需要考虑左右安全区适配
个人总结的混合开发方式:
原生混合开发: web-view套在 android app 或 ios app 中。这种方式直接让 android/ios开发通过编程方式,告知状态栏或者说刘海的高度即可
小程序原生开发: 如:微信小程序。他们通常都提供了专门的API,来获取状态栏高度。
uni app开发: 也有专门的API,来获取状态栏高度
如何适配
用 CSS 环境变量(推荐)
现代 WebView / 浏览器已经支持 env(safe-area-inset-*),这样能自动适配不同设备的安全区:
.safe-area {
padding-top: constant(safe-area-inset-top); /* 兼容旧 iOS */
padding-top: env(safe-area-inset-top); /* 标准写法 */
padding-bottom: env(safe-area-inset-bottom);
}这样,iOS 刘海屏、安卓挖孔屏都会自动读取系统安全区。
动态获取状态栏高度(App 内嵌 H5 常用)
如果你这个页面是跑在 App WebView 里,可以通过 JSBridge / SDK 获取状态栏高度,然后加到样式里:
document.documentElement.style.setProperty(
'--status-bar-height',
statusBarHeight + 'px'
);CSS 里这样用:
.nav-bar {
padding-top: var(--status-bar-height);
}方案 3:UI 框架内置的 SafeArea
比如 Vant、Taro、UniApp 等框架里,都有内置的 <SafeArea> 或类似组件,可以直接包裹内容,它会自动给顶部/底部留出安全区。
例子(Vant 里用 safe-area-inset-top class):
<div class="van-safe-area-inset-top"></div>- 如果是 H5 单独运行,用
env(safe-area-inset-*)是最简单的方案。 - 如果是 嵌在 App 里,建议让原生端给你传状态栏高度(更精确)。
- 如果用了 UI 框架,直接用它的 SafeArea 组件,避免重复造轮子。
要不要我帮你把你代码里写死的 44px / 46px 都替换成 动态安全区方案(用 env 或者 CSS 变量)?
评论
评论加载中……