Vue样式篇

Vue样式篇

📦 Vue3

style

  • 全称v-bind:style,简写:style

😃 如果样式需要浏览器前缀,Vue 会自动加上

也可以自己传递

1
<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>
  • 如果浏览器不需要前缀,结果就只有display: flex

传递对象

  • 可用 camelCase 或者 kebab-cased

使用:style="{}"或者是:style="[]"绑定的样式

记得值也需要用 ""包住,React 中就不用。如果需要使用模板字符串,双引号换成**`**

常见使用:

1
2
3
4
5
6
7
8
9
10
11
12
const activeColor = ref("red");
const fontSize = ref(30);

const styleObject = reactive({
color: "red",
fontSize: "13px",
});

const isBig = ref(false);
const bigActive = computed(() => {
isBig ? { fontSize: "18px" } : { fontSize: "14px" };
});
1
2
3
4
5
6
7
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>

<div :style="{ 'font-size': fontSize + 'px' }"></div>

<div :style="styleObject"></div>

<div :style="isBig"></div>

传递数组

在 JS 中写好的样式对象传递到行内的数组中

1
<div :style="[baseStyles, overridingStyles]"></div>

在组件上使用style,组件内的根元素会继承组件上的样式。👉class 透传

class

  • 全称v-bind:class,简写:class

记得值也需要用 ""包住

class:class可同时存在,渲染后合并

1
<div class="main-pane" :class="{ active: isActive }"></div>
  • 样式类active是否存在,取决于isActive是否为 true
  • style一样,支持绑定对象或者计算属性,也支持数组

绑定数组的时候,还可以在数组里面嵌套对象

1
<div :class="[{ active: isActive }, errorClass]"></div>

组件上的 class

对于单根组件,组件上的class会和组件内根元素的合并

1
2
<!-- 子组件模板 -->
<p class="foo bar">Hi!</p>
1
2
<!-- 在使用组件时 -->
<MyComponent class="baz boo" />

渲染结果

1
<p class="foo bar baz boo">Hi!</p>

对于多根组件,在组件内部需要使用$attr.class来指定哪个根元素来接收

1
2
3
<!-- MyComponent 模板使用 $attrs 时 -->
<p :class="$attrs.class">Hi!</p>
<span>This is a child component</span>
1
<MyComponent class="baz" />

要在 JS 中获取到,可以通过useAttrs()

setup

1
2
3
4
5
<script setup>
import { useAttrs } from "vue";

const attrs = useAttrs();
</script>

不用setup的话

1
2
3
4
5
6
export default {
setup(props, ctx) {
// 透传 attribute 被暴露为 ctx.attrs
console.log(ctx.attrs);
},
};

不能用监听器来监听 attrs 的变化

<style>标签

css 预处理器

<style lang='xx'></style>xx一般会有 less, sass(使用的时候传scss)。

1
2
3
4
5
<style lang="scss">
.example {
color: red;
}
</style>

样式作用域

<style scoped></style>:使得里面的样式仅在当前组件可用

  • 在同一组件中,可和代表全局<style></style>同时存在
  • 谨慎使用后代选择器
1
2
3
4
5
6
7
8
9
<style scoped>
.example {
color: red;
}
</style>

<template>
<div class="example">hi</div>
</template>

转换为:

1
2
3
4
5
6
7
8
9
<style>
.example[data-v-f3f3eg9] {
color: red;
}
</style>

<template>
<div class="example" data-v-f3f3eg9>hi</div>
</template>
  • data-v-f3f3eg9 后面的哈希值根据文件的路径生成

使用了作用域之后,父组件样式不会影响子组件。不过为了布局考虑,子组件的根节点会受到影响

深度选择器:deep()

scoped作用域里面,要选择子组件里面的内容,使用伪类:deep()

1
2
3
4
5
6
7
8
9
10
<style scoped>
.a :deep(.b) {
/* ... */
}

/* 将会编译为 */
.a[data-v-f3f3eg9] .b {
/* ... */
}
</style>

括号里面的内容不需要用引号包起来

插槽选择器:slotted

默认情况下,scoped不会影响到<slot/>渲染出来的内容,因为它们被认为是父组件所持有并传递进来的。

要指定插槽的额内容

1
2
3
4
5
<style scoped>
:slotted(div) {
color: red;
}
</style>

全局选择器:global

就是让当前指定的选择器配置的样式全局范围内生效

1
2
3
4
5
<style scoped>
:global(.red) {
color: red;
}
</style>

CSS Modules

<style module="xxx">被编译成 CSS Module

  • 在 CSS Module 里的内容会变成对象
    • 直接module,使用默认名称$style
    • 自定义名称module="classes"
  • 可在<template><script setup>中使用

模板中使用

1
2
3
4
5
6
7
8
9
<template>
<p :class="$style.red">This should be red</p>
</template>

<style module>
.red {
color: red;
}
</style>
  • 绑定的 class会进行哈希化

setup 中使用

1
2
3
4
5
6
7
8
import { useCssModule } from "vue";

// 在 setup() 作用域中...
// 默认情况下, 返回 <style module> 的 class
useCssModule();

// 具名情况下, 返回 <style module="classes"> 的 class
useCssModule("classes");

CSS 中的 v-bind

<style>中用来绑定<script>中的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script setup>
const theme = {
color: "red",
};
</script>

<template>
<p>hello</p>
</template>

<style scoped>
p {
// 是表达式,就需要用引号包起来
color: v-bind("theme.color");
}
</style>

动画

Vue 提供组件 <Transition><TransitionGroup> 来处理元素的

  • 进入
  • 离开
  • 列表顺序变化

的过渡效果

  • <Transition>,组件或元素进入离开DOM 时应用动画
  • <TransitionGroup>,在一个v-for列表的元素或组件被插入移动删除时应用动画

<Transition>组件

仅支持单个元素或者一个单根组件

动画通过默认插槽传递给它包裹的元素或者组件上。触发条件:

  • v-if
  • v-show
  • <component>切换的时候
  • 改变特殊的key属性

整个动画过程:

  1. Vue 会自动检测目标元素是否应用了 CSS 过渡或动画。则一些 CSS过渡classv-enter那些)会在适当的时机被添加和移除。
  2. 如果有作为监听器的 JavaScript钩子,这些钩子函数会在适当时机被调用。
  3. 上面两点都没,那么 DOM 的插入、删除操作将在浏览器的下一个动画帧后执行。

🚗 可用的属性和事件

属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
interface TransitionProps {
/**
* 用于自动生成过渡 CSS class 名。
* 例如 `name: 'fade'` 将自动扩展为 `.fade-enter`、
* `.fade-enter-active` 等。
*/
name?: string;
/**
* 是否应用 CSS 过渡 class。
* 默认:true
*/
css?: boolean;
/**
* 指定要等待的过渡事件类型
* 来确定过渡结束的时间。
* 默认情况下会自动检测
* 持续时间较长的类型。
*/
type?: "transition" | "animation";
/**
* 显式指定过渡的持续时间。
* 默认情况下是等待过渡效果的根元素的第一个 `transitionend`
* 或`animationend`事件。
*/
duration?: number | { enter: number; leave: number };
/**
* 控制离开/进入过渡的时序。
* 默认情况下是同时的。
*/
mode?: "in-out" | "out-in" | "default";
/**
* 是否对初始渲染使用过渡。
* 默认:false
*/
appear?: boolean;

/**
* 用于自定义过渡 class 的 prop。
* 在模板中使用短横线命名,例如:enter-from-class="xxx"
*/
enterFromClass?: string;
enterActiveClass?: string;
enterToClass?: string;
appearFromClass?: string;
appearActiveClass?: string;
appearToClass?: string;
leaveFromClass?: string;
leaveActiveClass?: string;
leaveToClass?: string;
}

事件

  • @before-enter
  • @before-leave
  • @enter
  • @leave
  • @appear
  • @after-enter
  • @after-leave
  • @after-appear
  • @enter-cancelled
  • @leave-cancelled (v-show only)
  • @appear-cancelled

基于 CSS 的过渡效果

CSS 过渡 class

图片来自 Vue - Transition

  1. v-enter-from:进入动画的起始状态。在元素插入之前添加,在元素插入完成后的下一帧移除。

  2. v-enter-active:进入动画的生效状态。应用于整个进入动画阶段。在元素被插入之前添加,在过渡或动画完成之后移除。这个 class 可以被用来定义进入动画的持续时间、延迟与速度曲线类型。

  3. v-enter-to:进入动画的结束状态。在元素插入完成后的下一帧被添加 (也就是 v-enter-from 被移除的同时),在过渡或动画完成之后移除。

  4. v-leave-from:离开动画的起始状态。在离开过渡效果被触发时立即添加,在一帧后被移除。

  5. v-leave-active:离开动画的生效状态。应用于整个离开动画阶段。在离开过渡效果被触发时立即添加,在过渡或动画完成之后移除。这个 class 可以被用来定义离开动画的持续时间、延迟与速度曲线类型。

  6. v-leave-to:离开动画的结束状态。在一个离开动画被触发后的下一帧被添加 (也就是 v-leave-from 被移除的同时),在过渡或动画完成之后移除。

使用name属性声明一个过渡效果名来代替前缀v:

1
2
3
<Transition name="fade">
...
</Transition>
  • name可动态绑定

搭配 css 自己的 tansition 属性;也可以使用 css 的animation,一般都在*-enter-active*-leave中使用

同时使用transitionanimation,需要在标签中使用type属性传入两者之一,以告诉 Vue 需要关注哪种动画类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/*
进入和离开动画可以使用不同
持续时间和速度曲线。
*/
.fade-enter-active {
transition: all 0.3s ease-out;
/** 或者 */
animation: bounce-in 0.5s;
}

.fade-leave-active {
transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1);
/** 或者 */
animation: bounce-in 0.5s reverse;
}

.fade-enter-from,
.fade-leave-to {
transform: translateX(20px);
opacity: 0;
}

@keyframes bounce-in {
/** */
}

🎇 动画效果会直接应用在子元素上,此外还可以选择指定更深层次的子元素来触发动画效果

1
2
3
4
5
6
7
<Transition name="nested">
<div v-if="show" class="outer">
<div class="inner">
Hello
</div>
</div>
</Transition>
1
2
3
4
5
6
7
8
9
10
11
12
/* 应用于嵌套元素的规则 */
.nested-enter-active .inner,
.nested-leave-active .inner {
transition: all 0.3s ease-in-out;
}

.nested-enter-from .inner,
.nested-leave-to .inner {
transform: translateX(30px);
opacity: 0;
}
/* ... 省略了其他必要的 CSS */

还可以单独拿出来,添加一个过渡延迟,以获得交错效果

1
2
3
4
/* 延迟嵌套元素的进入以获得交错效果 */
.nested-enter-active .inner {
transition-delay: 0.25s;
}

通过向标签传递duration来指定过渡的持续时间,以确保这个延迟会在这个动画周期中生效(如果在transitionendanimationend时间结束了,延迟都没到来)

1
2
3
<Transition :duration="550">...</Transition>
<!-- 也可以传递一个对象来分别指定进入和离开 -->
<Transition :duration="{ enter: 500, leave: 800 }">...</Transition>

自定义过渡 class

<Transition> 传递以下的 props 来指定自定义的过渡 class:

  • enter-from-class
  • enter-active-class
  • enter-to-class
  • leave-from-class
  • leave-active-class
  • leave-to-class

在使用第三方 CSS 动画库的时候非常有用

1
2
3
4
5
6
7
8
<!-- 假设你已经在页面中引入了 Animate.css -->
<Transition
name="custom-classes"
enter-active-class="animate__animated animate__tada"
leave-active-class="animate__animated animate__bounceOutRight"
>
<p v-if="show">hello</p>
</Transition>

JavaScript 钩子

也可与听过监听<Transition>组件上的事件来实现动画

1
2
3
4
5
6
7
8
9
10
11
12
<Transition
@before-enter="onBeforeEnter"
@enter="onEnter"
@after-enter="onAfterEnter"
@enter-cancelled="onEnterCancelled"
@before-leave="onBeforeLeave"
@leave="onLeave"
@after-leave="onAfterLeave"
@leave-cancelled="onLeaveCancelled"
>
<!-- ... -->
</Transition>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 在元素被插入到 DOM 之前被调用
// 用这个来设置元素的 "enter-from" 状态
function onBeforeEnter(el) {}

// 在元素被插入到 DOM 之后的下一帧被调用
// 用这个来开始进入动画
function onEnter(el, done) {
// 调用回调函数 done 表示过渡结束
// 如果与 CSS 结合使用,则这个回调是可选参数
done();
}

// 当进入过渡完成时调用。
function onAfterEnter(el) {}

// 当进入过渡在完成之前被取消时调用
function onEnterCancelled(el) {}

// 在 leave 钩子之前调用
// 大多数时候,你应该只会用到 leave 钩子
function onBeforeLeave(el) {}

// 在离开过渡开始时调用
// 用这个来开始离开动画
function onLeave(el, done) {
// 调用回调函数 done 表示过渡结束
// 如果与 CSS 结合使用,则这个回调是可选参数
done();
}

// 在离开过渡完成、
// 且元素已从 DOM 中移除时调用
function onAfterLeave(el) {}

// 仅在 v-show 过渡中可用
function onLeaveCancelled(el) {}

使用仅由 JavaScript 执行的动画时,可添加一个 :css="false",告诉 Vue 可跳过对 CSS 过渡的自动检测。使用之后:

  • @enter@leave回调函数done是必须的,否则钩子会被同步调用,过渡将立即完成

可复用

😃 可以向标签里面传递<slot>来封装成可复用的过渡组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- MyTransition.vue -->
<script>
// JavaScript 钩子逻辑...
</script>

<template>
<!-- 包装内置的 Transition 组件 -->
<Transition name="my-transition" @enter="onEnter" @leave="onLeave">
<slot></slot>
<!-- 向内传递插槽内容 -->
</Transition>
</template>

<style>
/*
必要的 CSS...
注意:避免在这里使用 <style scoped>
因为那不会应用到插槽内容上
*/
</style>
1
2
3
<MyTransition>
<div v-if="show">Hello</div>
</MyTransition>

其他过渡效果

出现时过渡

初次渲染时应用一个过渡效果

1
2
3
<Transition appear>
...
</Transition>

元素间过渡

使用 v-if / v-else / v-else-if在几个组件之间切换

1
2
3
4
5
<Transition>
<button v-if="docState === 'saved'">Edit</button>
<button v-else-if="docState === 'edited'">Save</button>
<button v-else-if="docState === 'editing'">Cancel</button>
</Transition>

过渡模式

想先执行离开动画,等它完成之后在执行元素的进入动画

1
2
3
<Transition mode="out-in">
...
</Transition>
  • 也支持in-out,但是不常用

组件之间过渡

1
2
3
<Transition name="fade" mode="out-in">
<component :is="activeComponent"></component>
</Transition>

性能考虑

多数使用transformopacity

  1. 因为不会影响到 DOM 结构
  2. 多数现代浏览器的transform动画会使用 GPU 进行硬件加速

相比之下heightmargin这些会触发 CSS 布局变动,计算消耗就高了。🌏 查看更多

<TransitionGroup>组件

<Transition>基本相同的属性、CSS 过渡 class、JavaScript 钩子

区别:

  • 默认情况下不会生成容器元素;通过tag指定一个元素作为容器元素来渲染(外层自动生成的包裹层)
  • _过渡模式_(mode)不可用
  • 列表中每个元素需要有唯一key
  • CSS 过渡 class 会被应用在列表上而不是容器元素上

DOM 模板(html 文件或者 JS 片段)中使用时,需要使用<transition-group>

1
2
3
4
5
<TransitionGroup name="list" tag="ul">
<li v-for="item in items" :key="item">
{{ item }}
</li>
</TransitionGroup>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.list-move, /* 对移动中的元素应用的过渡 */
.list-enter-active,
.list-leave-active {
transition: all 0.5s ease;
}

.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translateX(30px);
}

/* 确保将离开的元素从布局流中删除
以便能够正确地计算移动的动画。 */
.list-leave-active {
position: absolute;
}

事件和<Transition>一样

属性对比<Transition>少了mode,其他一样,并且多了两个:

1
2
3
4
5
6
7
8
9
10
11
interface TransitionGroupProps extends Omit<TransitionProps, "mode"> {
/**
* 如果未定义,则渲染为片段 (fragment)。
*/
tag?: string;
/**
* 用于自定义过渡期间被应用的 CSS class。
* 在模板中使用 kebab-case,例如 move-class="xxx"
*/
moveClass?: string;
}

渐进延迟列表动画

  • 通过读取元素的 data attribute

把每一个元素的索引渲染为该元素上的一个 data attribute

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<TransitionGroup
tag="ul"
:css="false"
@before-enter="onBeforeEnter"
@enter="onEnter"
@leave="onLeave"
>
<li
v-for="(item, index) in computedList"
:key="item.msg"
:data-index="index"
>
{{ item.msg }}
</li>
</TransitionGroup>

在 JavaScript 钩子中,基于当前元素的 data attribute 对元素的进场动画添加一个延迟

1
2
3
4
5
6
7
8
function onEnter(el, done) {
gsap.to(el, {
opacity: 1,
height: "1.6em",
delay: el.dataset.index * 0.15,
onComplete: done,
});
}
作者

dsjerry

发布于

2023-09-19

更新于

2023-09-24

许可协议

评论