大眼动效
闲来无事,查阅文章,突然发现一个好玩的大眼动效,先来看一下效果吧。
大眼动效
绘制眼睛
这里的眼睛使用echarts图表中的仪表盘进行绘制,所以在项目初始的时候需要安装echarts的插件,因为我这嫌麻烦所以直接在main.js中进行全局引入了,如果不想全局的话可以直接通过echarts官网看文档进行按需引入
import * as echarts from 'echarts'
Vue.prototype.$echarts = echart
引入echarts等准备工作做好之后就开始进行眼睛的绘制工作了。先看一下正常的眼睛是个什么样子的吧
这个眼睛很简单,外层一个圆,内层就是echarts进行绘制
<div class="box">
<div class="eyeSocket" id='bigEye'>
<div id="eyeball" @click="sleepClick"></div>
</div>
</div>
这里用的scss,以及css变量,应该不用多说什么吧。css变量初始就用吧,可别嫌麻烦不用,不然后期就要麻烦咯。
$eyeSocket: var(--c-eyeSocket, rgb(41, 104, 217));
$eyeSocket-outer: var(--c-eyeSocket-outer, #02ffff);
$eyeSocket-outer-shadow: var(--c-eyeSocket-outer-shadow, transparent);
$eyeSocket-inner: var(--c-eyeSocket-inner, rgb(35, 22, 140));
.box {
width: 100vw;
height: 100vh;
overflow: hidden;
background-color: #111;
perspective: 1000px;
.eyeSocket {
width: 150px;
height: 150px;
position: absolute;
left: calc(50% - 75px);
top: calc(50% - 75px);
border-radius: 50%;
border: 4px solid $eyeSocket;
z-index: 1;
/* 添加过渡效果 */
#eyeball {
width: 100%;
height: 100%;
}
}
.eyeSocket::before,
.eyeSocket::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border-radius: 50%;
box-sizing: border-box;
}
.eyeSocket::before {
width: calc(100% + 20px);
height: calc(100% + 20px);
border: 6px solid $eyeSocket-outer;
}
}
到这里大眼的外框已经绘制完成了,接下来就要点睛了,毕竟眼睛眼睛,不能只有眼眶没有眼珠对吧。
在methods里面构建一个getChart函数,然后在mounted里面进行调用,echarts绘制的眼珠就出来了。echarts写法有不少,我这只是其中一种,你们可以按照自己的写法来写只要能实现就行
// 初始渲染
getChart() {
let eyeball = document.getElementById("eyeball")
let eyeballChart = this.$echarts.init(eyeball)
eyeballChart.setOption({
series: [{
type: 'gauge', // 使用仪表盘类型
radius: '-3%', // 采用负数是为了让分割线从内向外延伸
clockwise: false,
startAngle: '0', // 起始角度
endAngle: '270', // 结束角度
splitNumber: 3, // 分割数量,会将270度分割为3份,所以有四根线
detail: false,
axisLine: {
show: false,
},
axisTick: false,
splitLine: {
show: true,
length: 12, // 分割线长度
lineStyle: {
shadowBlur: 20, // 阴影渐变
shadowColor: 'rgb(0, 238, 255)', // 阴影颜色
shadowOffsetY: '0',
color: 'rgb(0, 238, 255)', // 分割线颜色
width: 4, // 分割线宽度
}
},
axisLabel: false
},
{
type: 'gauge',
radius: '-3%',
clockwise: false,
startAngle: '45', // 倾斜45度
endAngle: '315',
splitNumber: 3,
detail: false,
axisLine: {
show: false,
},
axisTick: false,
splitLine: {
show: true,
length: 12,
lineStyle: {
shadowBlur: 20,
shadowColor: 'rgb(0, 238, 255)',
shadowOffsetY: '0',
color: 'rgb(0, 238, 255)',
width: 4,
}
},
axisLabel: false
}
]
})
},
大眼要睡觉了
午休时间到了,大眼要开始睡觉了,睡觉呢肯定是需要闭眼的,同时呼吸也是必不可少的。呼吸的动效是通过animation动画来实现,不罗嗦上代码。
.eyeSocketSleeping {
animation: sleeping 6s infinite;
}
@keyframes sleeping {
0% {
transform: scale(1);
}
50% {
transform: scale(1.2);
}
100% {
transform: scale(1);
}
}
写完的朋友会发现,哎,我的大眼怎么不动呢。当然不会动了,因为html中缺少了让大眼睛动起来的class。那么又有朋友会说了,直接加上不就行了。直接加上当然没问题了,但是呢在整体上是不需要直接加的,会通过操作demo来控制,继续往下看吧
// 动态修改echarts内部属性值
getEyeballChart(l, b) {
let eyeball = document.getElementById("eyeball")
let eyeballChart = this.$echarts.init(eyeball)
let leftRotSize = l
let ballSize = b
let ballColor = this.ballColor; // 默认透明,其实默认是啥都无所谓,反正看不见
eyeballChart.setOption({
series: [
{
startAngle: `${0 + leftRotSize * 5}`, // 加为逆时针旋转,乘5表示速度为leftRotSize的倍
endAngle: `${270 + leftRotSize * 5}`, // 即变为每10微秒移动0.5度,1234678同理
splitLine: {
length: ballSize, // 分割线高度设置为眼球尺寸变量
lineStyle: {
shadowColor: ballColor, // 把眼睛的眼影颜色设为变量控制
color: ballColor,
}
},
},
{
startAngle: `${45 + leftRotSize * 5}`,
endAngle: `${315 + leftRotSize * 5}`,
splitLine: {
length: ballSize,
lineStyle: {
shadowColor: ballColor,
color: ballColor,
}
}
},
]
})
},
toSleep() {
let bigEye = document.getElementById("bigEye")
let leftRotSize = this.leftRotSize;
let ballSize = this.ballSize;
var rotTimer
// 进入睡眠状态
console.log("sleep");
clearInterval(rotTimer); // 清除定时器
rotTimer = setInterval(() => {
this.getEyeballChart(leftRotSize, ballSize)
if (ballSize > 0) {
ballSize -= 0.1; // 当眼球存在时慢慢减小
} else {
clearInterval(rotTimer);
bigEye.className = 'eyeSocket eyeSocketSleeping'; // 眼球消失后添加呼吸
}
leftRotSize === 360 ? (leftRotSize = 0) : (leftRotSize += 0.1); // 旋转,
}, 20)
},
到这里呢离着大眼进入睡眠状态就差临门一脚了
//首先data里面需要创建后续用到的公共属性
data() {
return {
isSleep: true,
leftRotSize: 0,
ballSize: 12,
ballColor: 'rgb(0, 238, 255)'
}
},
//其次就是需要初始化加载了,切记切记在调用动态修改echarts数据的方法时记得传递参数
mounted() {
this.getChart()
this.getEyeballChart(this.leftRotSize, this.ballSize)
this.toSleep()
},
OK,到这里那么恭喜你大眼成功进入了睡眠状态,均匀的呼吸表示此时大眼已经进入了深度睡眠状态了。
此时如果我们突然把大眼给弄醒了呢?哦,我们的大眼有起床气,快跑,小心挨揍
大眼被吵醒,大眼很生气
大眼被吵醒,那么肯定是需要条件的,所以加一个点击事件作为吵醒大眼的条件,点击事件已经在开始的时候写入了
在这里我们改变一下toSleep事件,默认情况下视为进入睡眠状态,一旦点击了之后,大眼就会立马切换为生气的状态。
// 默认视为开启睡眠状态、点击后进入起床状态
toSleep() {
let bigEye = document.getElementById("bigEye")
let leftRotSize = this.leftRotSize;
let ballSize = this.ballSize;
var rotTimer
// 进入睡眠状态
if (this.isSleep) {
console.log("sleep");
clearInterval(rotTimer); // 清除定时器
rotTimer = setInterval(() => {
this.getEyeballChart(leftRotSize, ballSize)
if (ballSize > 0) {
ballSize -= 0.1; // 当眼球存在时慢慢减小
} else {
clearInterval(rotTimer);
bigEye.className = 'eyeSocket eyeSocketSleeping'; // 眼球消失后添加呼吸
}
leftRotSize === 360 ? (leftRotSize = 0) : (leftRotSize += 0.1); // 旋转,
}, 20);
} else {
// 起床状态
console.log("weakup");
bigEye.className = 'eyeSocket'; // 清除休眠状态
this.setAngry() // 调用生气状态下的样式
clearInterval(rotTimer); // 清除定时器
rotTimer = setInterval(() => {
this.getEyeballChart(leftRotSize, ballSize)
if (ballSize <= 50) {
ballSize += 1
} else {
// clearInterval(rotTimer);
}
leftRotSize === 360 ? (leftRotSize = 0) : (leftRotSize += 0.5);
}, 10);
}
},
// 点击唤醒
sleepClick() {
if (!this.isSleep) return
this.ballSize = 0
this.isSleep = false
this.toSleep()
},
// 生气模式
setAngry() {
// 通过js修改body的css变量
document.body.style.setProperty('--c-eyeSocket', 'rgb(255,187,255)');
document.body.style.setProperty('--c-eyeSocket-outer', 'rgb(238,85,135)');
document.body.style.setProperty('--c-eyeSocket-outer-shadow', 'rgb(255, 60, 86)');
document.body.style.setProperty('--c-eyeSocket-inner', 'rgb(208,14,74)');
this.ballColor = 'rgb(208,14,74)';
},
此时的大眼眼睛通红,显得非常的可怕,但是大眼的怒气还在上升,感觉大眼要彻底发火了
在这里我是通过svg添加滤镜来实现的,此时还需要一个大眼的替身,通过改变大眼替身的形态来实现发火的效果
<div class="filter">
<div id="eyeFilter" class="eyeSocket"></div>
</div>
<!-- Svg滤镜 -->
<svg width="0">
<filter id='filter'>
<feTurbulence baseFrequency="1">
<animate id="animate1" attributeName="baseFrequency" dur="1s" from="0.5" to="0.55"
begin="0s;animate1.end">
</animate>
<animate id="animate2" attributeName="baseFrequency" dur="1s" from="0.55" to="0.5"
begin="animate2.end">
</animate>
</feTurbulence>
<feDisplacementMap in="SourceGraphic" scale="50" xChannelSelector="R" yChannelSelector="B" />
</filter>
</svg>
.filter {
width: 100%;
height: 100%;
/* 开启滤镜 */
filter: url('#filter');
.eyeSocket {
opacity: 0;
left: calc(50% - 92px);
top: calc(50% - 92px);
transition: all 0.5s ease-in-out;
}
}
此时的大眼是不是更加可怕了呢,但这还不够,大眼被人吵醒非常的生气,它要找找是谁吵醒的它,它已经准备好发泄内心的怒火了。
此处还是通过css动画来实现一个大眼四处找人的情景
.eyeSocket {
width: 150px;
height: 150px;
position: absolute;
left: calc(50% - 75px);
top: calc(50% - 75px);
/* aspect-ratio: 1; 长宽比 1:1 如果浏览器不支持该属性,换成 height: 150px 也一样 */
border-radius: 50%;
border: 4px solid $eyeSocket;
z-index: 1;
box-shadow: 0px 0px 50px $eyeSocket-outer-shadow;
/* 当生气时添加红色外发光,常态则保持透明 */
transition: border 0.5s ease-in-out, box-shadow 0.5s ease-in-out;
/* 添加过渡效果 */
#eyeball {
width: 100%;
height: 100%;
}
}
.eyeSocketLooking {
animation: lookAround 2.5s; // 添加动画,只播放一次
}
@keyframes lookAround {
0% {
transform: translateX(0) rotateY(0);
}
10% {
transform: translateX(0) rotateY(0);
}
40% {
transform: translateX(-70px) rotateY(-30deg);
}
80% {
transform: translateX(70px) rotateY(30deg);
}
100% {
transform: translateX(0) rotateY(0);
}
}
到现在为止,css样式已经全部完结,剩下的就是通过js去实现了,上述发火后四处看的情况还需要js去添加一下class
toSleep() {
// 进入睡眠状态
if (this.isSleep) {
} else {
//新增代码,控制是否执行发火后四处看的效果
eyeFilter.className = bigEye.className = 'eyeSocket eyeSocketLooking'
eyeFilter.style.opacity = '1'
}
},
火气消散,开始工作
生了一会气之后的大眼发现不能带着情绪进行工作,所以它进行了自我调整,自己调整好情绪之后就要开始工作了。
调节情绪,火气消散
在这里呢首先是通过监听动画结束事件webkitAnimationEnd来监听到四处看的动画结束,动画结束也就是代表已经生完气了,需要进行自我调整了,所以自我调整是放在这个监听里面,同时使用promise来确保事件执行的先后顺序。 消火=>恢复正常
toSleep() {
// 进入睡眠状态
....同上
if (this.isSleep) {
.....同上
} else {
//新增代码
bigEye.addEventListener("webkitAnimationEnd", () => {
new Promise(res => {
console.log("解除生气状态");
clearInterval(rotTimer); // 清除定时器
rotTimer = setInterval(() => {
this.getEyeballChart(leftRotSize, ballSize); // 更新视图
ballSize > 0 && (ballSize -= 0.5); // 眼球尺寸减小
leftRotSize === 360 ? (leftRotSize = 0) : (leftRotSize += 0.1);
if (ballSize === 0) { // 当眼球尺寸为0时,将Promise标记为resolved,然后执行后面的代码
clearInterval(rotTimer);
res();
}
}, 20);
}).then(() => {
console.log("开启常态模式");
var th = this
eyeFilter.style.opacity = '0'; // 隐藏掉粒子效果
eyeFilter.className = bigEye.className = 'eyeSocket'; // 清除环视动画
this.setNormal(); // 设置常态样式
rotTimer = setInterval(() => {
this.getEyeballChart(leftRotSize, ballSize); // 更新视图
ballSize <= 12 && (ballSize += 0.1); // 眼球尺寸缓慢增加
leftRotSize === 360 ? (leftRotSize = 0) : (leftRotSize += 0.1);
}, 20);
})
})
}
},
// 常态模式
setNormal() {
document.body.style.setProperty('--c-eyeSocket', 'rgb(41, 104, 217)');
document.body.style.setProperty('--c-eyeSocket-outer', '#02ffff');
document.body.style.setProperty('--c-eyeSocket-outer-shadow', 'transparent');
document.body.style.setProperty('--c-eyeSocket-inner', 'rgb(35, 22, 140)');
this.ballColor = 'rgb(0,238,255)';
},
开始工作
开始工作的状态是一个跟随鼠标移动,进而实现眼睛眼球转动的一个效果,此时就需要监听鼠标移动。
这里顺道说一下,window.requestAnimationFrame()是推迟到浏览器下一次重绘时就执行回调。重绘通常是 16ms 执行一次,不过浏览器会自动调节这个速率,比如网页切换到后台 Tab 页时,requestAnimationFrame()会暂停执行。
如果某个函数会改变网页的布局,一般就放在window.requestAnimationFrame()里面执行,这样可以节省系统资源,使得网页效果更加平滑。因为慢速设备会用较慢的速率重流和重绘,而速度更快的设备会有更快的速率。
toSleep() {
// 进入睡眠状态
....同上
if (this.isSleep) {
.....同上
} else {
//新增代码
bigEye.addEventListener("webkitAnimationEnd", () => {
new Promise(res => {
...同上
}).then(() => {
...同上
// 当恢复正常状态后就可以进入工作状态了
if (ballSize >= 12) {
document.getElementsByTagName("body")[0].addEventListener('mousemove', (e) => {
window.requestAnimationFrame(function () {
th.focusMouse(e);
});
});
}
}, 20);
})
})
}
},
// 跟随鼠标移动
focusMouse(e) {
let bigEye = document.getElementById("bigEye")
let eyeball = document.getElementById('eyeball')
// 视口尺寸,获取到整个视口的大小
let clientWidth = document.body.clientWidth
let clientHeight = document.body.clientHeight
// 原点,即bigEye中心位置,页面中心
let origin = [clientWidth / 2, clientHeight / 2]
// 鼠标坐标
let mouseCoords = [e.clientX - origin[0], origin[1] - e.clientY]
// 旋转角度
let eyeXDeg = mouseCoords[1] / clientHeight * 80; // 这里的80代表的是最上下边缘大眼X轴旋转角度
let eyeYDeg = mouseCoords[0] / clientWidth * 60;
bigEye.style.transform = `rotateY(${eyeYDeg}deg) rotateX(${eyeXDeg}deg)`;
eyeball.style.transform = `translate(${eyeYDeg / 1.5}px, ${-eyeXDeg / 1.5}px)`;
},
结尾
以上就是大眼整个午休时的情景了,大体的流程就是 开始睡觉=>被吵醒=>吵醒后开始生气=>生气后开始找人=>发泄完之后开始消火=>进行自我调整=>开始工作。我的这个也是根据其他人的来转成vue格式的罢了,大家如果有什么想法或者文章中有什么问题都可以进行评论,技术需要交流的吗。
需要完整代码的可以来这里 https://download.csdn.net/download/War__Tiger/87257396