VUE&CSS原理

Vue 里的 <style scoped> 作用是:让当前组件的样式只作用于当前组件的 DOM,不影响全局。

scope原理

scoped 的作用

Vue 里的 <style scoped> 作用是:让当前组件的样式只作用于当前组件的 DOM,不影响全局。 实现方式并不是运行时隔离,而是 编译阶段给 DOM 和 CSS 选择器加特殊标记

2. 基本例子

html
<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):

html
<div class="parent" data-v-123abc>
  <p class="child" data-v-123abc>Hello</p>
</div>
样式编译后的 CSS

对应的 CSS 选择器也会被加上 [data-v-123abc],确保只匹配带这个属性的元素:

css
.parent[data-v-123abc] {
  color: red;
}
.child[data-v-123abc] {
  font-size: 20px;
}

这样,哪怕全局也有 .child 类名,它也不会被这里的样式覆盖。

3. ::v-deep 的特殊处理

如果我们加一个子组件 <Child />,想影响它内部的 .title,直接写会失效:

css
<style scoped>
.title {
  color: blue;
}
</style>

编译后会变成:

css
.title[data-v-123abc] { color: blue; }

子组件里的 DOM 不会有 data-v-123abc,所以样式无法命中。

使用 ::v-deep

css
<style scoped>
::v-deep(.title) {
  color: blue;
}
</style>

编译后会变成:

css
[data-v-123abc] .title {
  color: blue;
}
它是一个 属性选择器 (attribute selector) + 普通类选择器的组合。
表示:选择所有在带有 data-v-123abc 的元素里面的,class 名为 title 的子元素。

这样就能影响到子组件的 .title 了。(而且是精准的子元素!不是全局的!)

deep形式讲解

Vue 2.x 初期

早期有几种写法:

>>>

css
.parent >>> .child { color: red; }
  • 属于 CSS combinator hack,Vue-loader 在编译时识别并替换。
  • 不过 >>> 在某些 CSS 预处理器(如 Sass)里会报错。

/deep/

css
.parent /deep/ .child { color: red; }
  • 来自 Shadow DOM 的穿透选择器,虽然标准后来废弃了,但 Vue 借鉴了它。
  • 编译时 /deep/ 会被替换成 [data-v-xxxx] 选择器,从而作用到子组件。

Vue 2.x 后期

出现了 ::v-deep(Vue-loader 15 之后加入),官方开始推荐这种写法:

css
::v-deep(.child) {
  color: red;
}

这时候,>>>/deep/ 依旧可用,但逐渐被认为是 过渡方案

Vue 3.x

官方 完全统一 样式穿透写法为 ::v-deep

语法更灵活,有两种主要形式:

  1. 函数式

    css
    .parent ::v-deep(.child) {
      color: red;
    }

    推荐写法,清晰直观。

  2. 包裹式

    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。

语法类似伪元素,所以有两个冒号:

css
.parent ::v-deep(.child) {
  color: red;
}

2. :deep(语法糖 / 简写)

<style scoped lang="scss"><style scoped lang="less"> 时, 因为 ::v-deep 在 Sass/LESS 编译时容易被当成伪元素报错,所以 Vue Loader 提供了 替代语法

css
.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-*),这样能自动适配不同设备的安全区:

css
.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 获取状态栏高度,然后加到样式里:

js
document.documentElement.style.setProperty(
  '--status-bar-height',
  statusBarHeight + 'px'
);

CSS 里这样用:

css
.nav-bar {
  padding-top: var(--status-bar-height);
}

方案 3:UI 框架内置的 SafeArea

比如 Vant、Taro、UniApp 等框架里,都有内置的 <SafeArea> 或类似组件,可以直接包裹内容,它会自动给顶部/底部留出安全区。 例子(Vant 里用 safe-area-inset-top class):

html
<div class="van-safe-area-inset-top"></div>
  • 如果是 H5 单独运行,用 env(safe-area-inset-*) 是最简单的方案。
  • 如果是 嵌在 App 里,建议让原生端给你传状态栏高度(更精确)。
  • 如果用了 UI 框架,直接用它的 SafeArea 组件,避免重复造轮子。

要不要我帮你把你代码里写死的 44px / 46px 都替换成 动态安全区方案(用 env 或者 CSS 变量)?

评论

评论加载中……