不一样的摸鱼

大眼动效

闲来无事,查阅文章,突然发现一个好玩的大眼动效,先来看一下效果吧。

大眼动效

绘制眼睛

这里的眼睛使用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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值