公共技巧 动画(css3、vue新增)和过渡和变形

388 阅读13分钟

过渡

transition : 要过渡的属性 花费时间 运动曲线 何时开始;

属性描述示例值
transition简写属性,用于在一个属性中设置四个过渡属性
transition-property规定应用过渡的 CSS 属性的名称width
transition-duration定义过渡效果花费的时间1.5s
transition-timing-function规定过渡效果的时间曲线
transition-delay规定过渡效果何时开始1.5s

transition-duration 值:

  • ease:默认值,缓慢加速,然后缓慢减速的过渡效果。
  • linear:匀速过渡效果。
  • ease-in:缓慢加速的过渡效果。
  • ease-out:缓慢减速的过渡效果。
  • ease-in-out:先缓慢加速,再缓慢减速的过渡效果。
  • cubic-bezier(n,n,n,n):自定义的贝塞尔曲线函数,通过四个值来定义曲线的控制点,分别对应 x1、y1、x2、y2。

使用例子:

// 如果有多个属性需要使用过渡效果:使用,隔开即可
transition: width 0.6s ease 0s, height 0.3s ease-in 1s;
// 如果想要所有的属性都变化过渡, 写一个 all 即可
transition: all 0.3s;
    

动画

在官方的介绍上介绍这个属性是transition属性的扩展,弥补了transition的很多不足,我理解为animation是由多个transition的效果叠加,并且可操作性更强,能够做出复杂酷炫的效果

动画有两种实现形式:原生以及vue自带的动画标签

css原生写法:

直接在需要动画的元素的类名里加上animation属性

.hjbtn,
.dyqrdbtn {
  position: relative;
  top: 0;
  animation: bounce 1s ease-in-out alternate;
}
@keyframes bounce {
  0% {
    transform: translateY(0) rotate(0deg) scale(1);
  }
  50% {
    transform: translateY(50px) rotate(180deg) scale(1.5);
  }
  100% {
    transform: translateY(0) rotate(360deg) scale(1);
  }
}

语法:

animation: name duration timing-function delay iteration-count direction fill-mode play-state;

animation-name指定要绑定到选择器的关键帧的名称
animation-duration动画指定需要多少秒或毫秒完成
animation-timing-function设置动画将如何完成一个周期
animation-delay设置动画在启动前的延迟间隔。
animation-iteration-count定义动画的播放次数。
animation-direction指定是否应该轮流反向播放动画。
animation-fill-mode规定当动画不播放时(当动画完成时,或当动画有一个延迟未开始播放时),要应用到元素的样式。
animation-play-state指定动画是否正在运行或已暂停。

动画标签

vue新增的两个动画标签:
  • transition
  • transition-group
<transition name="bounce" appear>
  <div v-if="1"></div>
</transition>

<transition-group name="bounce" appear tag="div">
  <div v-if="1" key=""></div>
  <div v-if="1" key=""></div>
</transition-group>

注意:如果使用 transition-group 标签,那么必须给标签内的所有元素都加上key (key可以为空)

标签属性:
  • name:标签标识/标签名(必写的)
  • appear:让元素第一次显示/创建的时候就触发
  • tag:将transition-group标签渲染成什么类型的元素

示例:

.bounce-enter-active {
  animation: bounce-in .5s;
}
.bounce-leave-active {
  animation: bounce-in .5s reverse;
}
@keyframes bounce-in {
  0% {
    transform: scale(0);
  }
  50% {
    transform: scale(1.5);
  }
  100% {
    transform: scale(1);
  }
}

bounce 就是 动画标签的标签名

  • enter-active:元素显示/创建的时候触发
  • leave-active:元素隐藏/移除的时候触发

页面元素常用动画示例

一般页面元素使用动画的场景可以分为3大类

  1. 元素显示/隐藏时的动画效果
  2. 鼠标移入移出时的动画效果
  3. 元素出现/离开浏览器的可视视口时的动画效果

示例代码效果

PixPin_2025-06-16_17-47-46.gif

示例代码

<template>
	<div class="box"></div>
	<div class="box"></div>
	<div class="tip">
		----------------------------------------元素出现/离开浏览器的可视视口时的动画效果开始---------------------------------------------
	</div>
	<div class="fade-element-box">
		<div class="fade-element">
			具备合法的工商注册手续、独立法人资格和独立账号,具有相应经营范围,能独立开展对外经营业务;
		</div>
	</div>
	<div class="scale-element"></div>
	<div class="tip">
		----------------------------------------元素出现/离开浏览器的可视视口时的动画效果结束---------------------------------------------
	</div>
	<div class="tip">
		----------------------------------------元素显示/隐藏时的动画效果开始---------------------------------------------
	</div>
	<div class="hover-element"></div>
	<div class="tip">
		----------------------------------------元素显示/隐藏时的动画效果结束---------------------------------------------
	</div>
	<div class="tip">
		----------------------------------------鼠标移入移出时的动画效果开始---------------------------------------------
	</div>
	<div class="menu-box">
		<div class="menu-item" v-for="(item, idx) in menuList" :key="idx">
			<div class="label" @mouseenter="handleEnter(idx)" @mouseleave="handleLeave(idx)">
				{{ item.label }}
			</div>
			<transition name="fade">
				<div class="value" v-if="showValues[idx]">
					<div class="value-item" v-for="v in item.values" :key="v">{{ v }}</div>
				</div>
			</transition>
		</div>
	</div>
	<div class="tip">
		----------------------------------------鼠标移入移出时的动画效果结束---------------------------------------------
	</div>
	<div class="box"></div>
</template>

<script setup>
	import { ref, onMounted, onBeforeUnmount, computed, watchEffect, getCurrentInstance } from 'vue';
	const { proxy } = getCurrentInstance();

	let observer = null;

	onMounted(() => {
		// 监听被观察元素是否进入视口
		observer = new IntersectionObserver(
			// 回调函数
			(entries) => {
				entries.forEach((entry) => {
					/*
					entry.isIntersecting 为 true 时,说明元素进入视口;为 false 时,说明元素离开视口
					entry.target.classList.toggle('ViewVisible', entry.isIntersecting) 的意思是:如果元素进入视口,就给它加上 ViewVisible 类;如果离开视口,就移除 ViewVisible 类。
					*/
					// 针对 fade-element
					if (entry.target.classList.contains('fade-element')) {
						entry.target.classList.toggle('ViewVisible', entry.isIntersecting);
					}
					// 针对 scale-element
					if (entry.target.classList.contains('scale-element')) {
						entry.target.classList.toggle('ViewVisible', entry.isIntersecting);
					}
				});
			},
			{ threshold: 0.1 } // 元素有 10% 可见时就会触发回调
		);
		/*
		1. 选中页面上所有指定的元素
		2. 对每个元素都调用 observer.observe(el),让 observer 监听它们的可见性变化。
		*/
		document
			.querySelectorAll('.fade-element, .scale-element')
			.forEach((el) => observer.observe(el));
	});

	const menuList = [
		{
			label: '菜单1',
			values: ['菜单1的内容1', '菜单1的内容2', '菜单1的内容3'],
		},
		{
			label: '菜单2',
			values: ['菜单2的内容1', '菜单2的内容2', '菜单2的内容3'],
		},
	];

	const showValues = ref(menuList.map(() => false));

	function handleEnter(index) {
		showValues.value[index] = true;
	}
	function handleLeave(index) {
		showValues.value[index] = false;
	}

	onBeforeUnmount(() => {
		if (observer) observer.disconnect();
	});
</script>

<style lang="scss" scoped>
	.box {
		width: 100%;
		height: 50vh;
		background-color: #000;
	}
	.tip {
		display: flex;
		justify-content: center;
		font-size: 28px;
	}

	.fade-element-box {
		height: 200px;
		width: 100%;
		background: #0b53db;
		padding: 40px;
	}
	.fade-element {
		color: #fff;
		font-weight: bold;
		font-size: 36px;
		&.ViewVisible {
			animation: fadeAnimation 2s ease-in-out;
		}
	}
	@keyframes fadeAnimation {
		from {
			opacity: 0;
			transform: translateY(300%);
		}
		to {
			opacity: 1;
			transform: translateY(0);
		}
	}

	.scale-element {
		width: 200px;
		height: 200px;
		background: #0ccf67;
		margin: 40px auto;
		&.ViewVisible {
			animation: scaleAnimation 2s ease-in-out;
		}
	}
	@keyframes scaleAnimation {
		from {
			transform: scale(0);
		}
		to {
			transform: scale(1);
		}
	}

	.hover-element {
		width: 100px;
		height: 100px;
		background: #e62222;
		margin: 40px auto;
		transition: transform 1s ease-in-out;
		&:hover {
			transform: scale(1.5);
		}
	}

	.menu-box {
		margin-top: 50px;
		.menu-item {
			display: flex;
			align-items: center;
			.label {
				font-size: 24px;
				font-weight: bold;
				color: #000;
				margin-right: 20px;
			}
			.value {
				display: flex;
				align-items: center;
				.value-item {
					font-size: 16px;
					color: #000;
					margin-right: 20px;
				}
			}
		}
	}
	.fade-enter-active {
		animation: fade-in 0.5s ease-in-out;
	}
	.fade-leave-active {
		animation: fade-in 0.5s ease-in-out reverse;
	}
	@keyframes fade-in {
		from {
			opacity: 0;
			transform: translateX(20px);
		}
		to {
			opacity: 1;
			transform: translateX(0);
		}
	}
</style>

变形转换

transform 2D变形

transform可以实现元素的位移、旋转、缩放、倾斜

倾斜 skew(deg, deg)

transform:skew(30deg,0deg);

该实例通过skew方法把元素水平方向上倾斜30度,垂直方向保持不变。

可以使元素按一定的角度进行倾斜,可为负值,第二个参数不写默认为0。

缩放 scale(x, y)

transform: scale(0.8,1);

可以对元素进行水平和垂直方向的缩放。该语句使用scale方法使该元素在水平方向上缩小了20%,垂直方向上不缩放。

scale(X,Y)使元素水平方向和垂直方向同时缩放(也就是X轴和Y轴同时缩放)
scale(n)如果只写一个值,表示XY轴同时缩放
scaleX(x)元素仅水平方向缩放(X轴缩放)
scaleY(y)元素仅垂直方向缩放(Y轴缩放)

scale()的取值默认的值为1,当值设置为0.01到0.99之间的任何值,作用使一个元素缩小;而任何大于1的值,作用是让元素放大 1.x倍

移动 translate(x, y)

translate 移动平移的意思。

  

transform: translate(50px,50px);

使用translate方法来将文字或图像在水平方向和垂直方向上分别垂直移动50像素。(可以改变元素的位置,x、y可为负值;)

 translate(x,y) 水平方向 和 垂直方向 同时移动(也就是X轴和Y轴同时移动)
 translate(n)如果只写一个值,表示只是X轴移动Y轴不移动,等价于:translate(x,0)
 translateX(x) 仅 水平方向 移动(X轴移动)
 translateY(Y) 仅 垂直方向 移动(Y轴移动)
旋转 rotate(deg)

可以对元素进行旋转,正值为顺时针,负值为逆时针;

// 注意deg是度数 ,记得写上
transform: rotate(45deg);

注意:在2D的情况下没有rotateX / rotateY,它俩和rotateZ都属于3D旋转

最后注意:

transform 可多个属性连写:

transform: translate(x,y) scale(1.5,1);/*可连写但有先后顺序,这里是先移动后伸缩*/

2D参考文章:blog.csdn.net/Ivy_Ch/arti…

transform 3D变形

首先先说明:3D的用法和2D是差不多的,无非是3D比2D多了个Z轴的值而已

使用一个盒子示例来讲解3D变形

c741130414244db7a00d8e47f3ec6e96~tplv-k3u1fbpfcp-zoom-in-crop-mark_1512_0_0_0.gif

CSS 3D坐标系

先来了解一下坐标系的概念。

从二维“反降维”到三维,需要理解下这个坐标系。

image.png

我们可以看到增加一个Z纬度的线,平面就变3D了。

这里需要注意的是CSS3D中,上下轴是Y轴,左右轴是X轴,前后轴是Z轴。可以简单理解为在原有竖着的面对我们的平面中,在X和Y轴中间强行插入一根直线,与Y轴和X轴都成90度,这根直线就是Z轴。

通过上面的处理,这样就形成了一个空间坐标系。

3D旋转 rotate

rotateX / rotateY / rotateZ / rotate3d

rotate3d 前3个参数分别表示是否旋转对应的坐标轴: x , y , z ,第 4 个参数表示旋转的角度,参数不允许省略。

例如:

transform: rotate3d(1,1,1,30deg) 意思是: x 、 y 、 z 分别旋转30度

transform: rotate3d(1, 0, 0, 45deg) 意思是: x轴旋转45度, y、z轴不变

下图展示了XYZ轴的旋转效果:X轴(正值向后负值向前)、Y轴(正值向右负值向左)、Z轴(正值向右负值向左)

v2-9afd0948d75bdec3929990320335d6e5_720w.gif

3D位移 translate

translateX / translateY / translateZ / translate3d

translate3d其实就是XYZ的集合写法:transform: translate3d(x, y, z);

下图展示了XYZ轴的移动效果:X轴左右移动、Y轴上下移动、Z轴前后移动(有点放大缩小的感觉,正值放大负值缩小)

v2-74f7624563207544a8904dd943361714_720w.gif

translateZ

translateZ实现了CSS3D世界空间的近大远小。

看一下这个例子,平面上的translateZ的变换

Kapture 2021-08-18 at 14.06.30.gif

比如,我们设置元素perspective为201px,则其子元素的translateZ值越小,则看着越小;如果translateZ值越大,则看着越大。当translateZ为200px的时候,该元素会撑满屏幕,当超过201px时候,该元素消失了,跑到我们眼睛后面了。

Z轴的概念

image.png

写了沿着Z轴位移(translateZ)但是浏览器页面中没有Z轴位移效果:Z轴是视线方向,但是人眼到屏幕的距离并没有具体的距离(我可以坐在电脑面前看电脑屏幕也可以站在1km外看电脑屏幕,所以视线距离没有清楚的定义出来),所以Z轴也没有实际距离,Z轴没有实际距离自然无法让标签沿着Z轴位移(通俗来讲:你都没有路,我怎么向前走呢?)。   image.png

下面给出解决Z轴没有位移效果的方法:使用perspective 透视属性;

perspective

image.png

解决Z轴没有位移效果:使用perspective 透视属性解决(注意这个透视属性是加给想要空间位移的盒子的父级盒子)

perspective 透视属性的值:取800px-1200px。(给人眼到屏幕加上800px-1200px的距离)

取得小了效果会非常明显:本来只是拉近一点距离,按道理来说是放大一些,结果由于人眼到屏幕很小,就会导致放大的效果非常明显,直接放大到占满屏幕了;

取得大了效果会非常不明显:本来只是拉远一点距离,按道理来说是缩小一些,结果由于人眼到屏幕很大,就会导致缩小的效果非常不明显,你根本看不出来缩小了;

Perspective 透视属性的解释:就是给人眼到屏幕之间加上一个具体的距离,让改变Z轴的距离可以实现(如果没有这个人眼到屏幕的具体距离的话,Z轴不管怎么改都无法生效:人眼到屏幕都没有距离,你Z轴拉远拉进都没有效果。通俗来讲就是:都没有做菜的食材,你烹饪技术再高超也无法做出菜来呀。所以想要Z轴的距离可以改变,就得给Z轴(人眼到屏幕)一个具体的距离,这样Z轴的距离才能被改变。)

总结:透视属性就是给Z轴创建出一条实际的距离,让Z轴拥有距离,Z轴有了距离之后,标签自然可以沿着Z轴进行位移。

开启3D空间

需要开启3D空间的盒子加上

  • 立体呈现属性 transform-style:preserve-3d;
  • 透视属性 perspective: 1000px;
下面用案例来详细讲解
导航:

肯定是使用ul li a 来布局导航。

第一步:搭建立方体

image.png

有2个面,所以要有2个a,2个a要立体展示,所以给装a的盒子(li)添加transform-style:preserve-3d 立体呈现属性,给li标签加上立体呈现属性后,li这个盒子就变成了立方体盒子。

先把立方体盒子做出来:给li加transform-style:preserve-3d 立体呈现属性和perspective: 1000px;透视属性

image.png

让li这个立方体盒子中,2个a分别处于立方体盒子的正面和上面;所以:

(1号a是橙色那个,2号a是绿色那个)

  1. 先用定位让2个a都出现在li立方体盒子的中心(看上图中的第2个小图)
  2. 让1号a绕X轴旋转90°(看上图中的第3个小图)
  3. 让1号a沿着Z轴向前位移(看上图中的第4个小图)注意,这里为什么是Z轴?因为在第二步中已经让1号a绕X轴旋转90°了,那么旋转过后的1号a的坐标轴相应的就改变了(坐标 轴是固定在标签的正面的,1号a旋转后,它的正面朝上,相应的它的坐标轴 也朝上,所以 如果让它再向上位移,这个向上对应的应该是向它的Z轴方向 向前移。)
  4. 让2号a沿着Z轴向前位移(看上图中的第5个小图)

再把立方体盒子的每个面都加上内容:把内容标签通过定位层叠在立方体盒子的中心,然后再通过空间位移、旋转把内容标签移动到立方体盒子的各个面上(上面是移动到正面、上面的思路,总共六个面,思路都一样。)

注意:旋转过后的标签的坐标轴方向和没有旋转之前的坐标轴方向是不一样的

第二步:让立方体盒子旋转

image.png 利用hover鼠标移动到立方体盒子上时,让立方体盒子旋转即可:使用的是空间旋转的代码:让立方体盒子绕着X轴旋转90°。

制作一个旋转的立方体盒子参考文章: