有趣的前端项目——一个暴躁萌的大眼仔
众所周知,我是一个摆子前端(真的 ),闲来无事,网上冲浪
遇见了如此蠢萌的大眼
于是我,行也思,坐也思,可算把这个大眼给复刻出来了。
原文出处:稀土掘金的一位博主——Urias
稀土掘金Urias
晓看天色暮看云,行也思君,坐也思君。 ——一剪梅·唐寅
同样,今天周一,在下也与诸位同道中人以来探讨这个话题::「 当一个前端空闲的时候会做些什么 」。
原博主说的我很赞同,我也非常喜欢在空闲的时间钻研(混 )前端技术(工作量 ) 。
先来看看我这的大眼萌萌的介绍:
- 名称:大眼
- 生成:2022-8-29
- 性别:无性
- 情绪:发怒/常态
- 状态:休眠/工作
- 简介:除了很萌,就是很萌,像猫猫一样,喜欢一直盯着你的鼠标,以防你找不到你的鼠标。但是大眼猫有起床气,而且非常懒散,容易犯困。
大眼的生活照:
✨画“大眼”,先画圆
“画人先画骨”,同样画大眼也得先画它的骨,没错,以普遍理性而论就是一个圆
还是和正常思路,先搞一个盒子,用来当骨
<div class="eyeSocket">
</div>
在添加一些必要的样式
body {
width: 100vw;
height: 100vh;
overflow: hidden;
background-color: #111;
}
.eyeSocket {
position: absolute;
left: calc(50% - 75px);
top: calc(50% - 75px);
width: 150px;
/* 长宽比 1:1 如果浏览器不支持该属性,换成 height: 150px 也一样 */
aspect-ratio: 1;
border-radius: 50%;
border: 4px solid var(--c-eyeSocket);
z-index: 1;
}
效果:
然后就是两个圆和一些阴影效果,为了不影响HTML的结构,所以用两个伪元素来实现
.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 #02ffff;
}
.eyeSocket::after {
width: 100%;
height: 100%;
border: 4px solid rgb(35, 22, 140);
box-shadow: inset 0px 0px 30px rgb(35, 22, 140);
}
✨创龙点睛 ?
大眼的眼球(骨)画好之后,就需要给他点上眼睛,想要什么样的样式因人而异,我就模仿原博主,采用仪表盘分割的方法来制作,分割线作为大眼的眼仁。
为了方便制作过度效果,小生使用的的是echarts来完成的。
echarts的引入方式非常简单
<script src="./js/echarts.js"></script>
接下来
在给眼仁一个容器,并且初始化echarts画布
<div class="eyeSocket">
<div id="eyeball"></div>
</div>
#eyeball {
width: 100%;
height: 100%;
}
画眼球
let eyeball = document.getElementById('eyeball');//获取元素
let eyeballChart = echarts.init(eyeball);//初始化画布
function getEyeballChart() {
eyeballChart.setOption({
series: [
{
type: 'gauge', // 使用仪表盘类型
radius: '-20%', // 采用负数是为了让分割线从内向外延伸
clockwise: false,
startAngle: `${0 + leftRotSize * 5}`, // 起始角度
endAngle: `${270 + leftRotSize * 5}`, // 结束角度
splitNumber: 3, // 分割数量,会将270度分割为3份,所以有四根线
detail: false,
axisLine: {
show: false,
},
axisTick: false,
splitLine: {
show: true,
length: ballSize, // 分割线长度
lineStyle: {
shadowBlur: 20, // 阴影渐变
shadowColor: ballColor, // 阴影颜色
shadowOffsetY: '0',
color: ballColor, // 分割线颜色
width: 4, // 分割线宽度
}
},
axisLabel: false
},
{
type: 'gauge',
radius: '-20%',
clockwise: false,
startAngle: `${45 + leftRotSize * 5}`,//倾斜角度
endAngle: `${315 + leftRotSize * 5}`,
splitNumber: 3,
detail: false,
axisLine: {
show: false,
length: ballSize,
},
axisTick: false,
splitLine: {
show: true,
length: ballSize,
lineStyle: {
shadowBlur: 20, // 阴影渐变
shadowColor: ballColor, // 阴影颜色
shadowOffsetY: '0',
color: ballColor, // 分割线颜色
width: 4,
}
},
axisLabel: false
}
]
});
}
//getEyeballChart() 可以解除注释看看效果
效果
这样,眼仁就画好了,小生并不经常使用echarts,所以画起来很费劲,但对于经常使用echarts的同学可以说是轻而易举。
以上
静态大眼已经画完,接下来要为大眼赋予生命周期了
✨生命仪式: 休眠状态
赋予生命是神圣的,她需要一个过程,所以小生从最简单的状态开始——「 休眠 」
休眠状态其实就是睡觉,自然是闭着眼睛睡觉,所以小生只有设计不露出眼仁的同时有节奏的呼吸(缩放)即可,相比于整个生命仪式来说,是较简单的一部分。
呼吸
小生使用的是 Css转换+动画的样式
<div class="eyeSocket eyeSocketSleeping">
<div id="eyeball"></div>
</div>
.eyeSocketSleeping {
animation: sleeping 4s infinite;
}
@keyframes sleeping {
0% {
transform: scale(1);
}
50% {
transform: scale(1.2);
}
100% {
transform: scale(1);
}
}
闭眼
既然是休眠,那一定有闭眼状态,所及接下就要想办法把大眼的眼睛闭上。之前用echarts设置的radius的值为负数,因为分割线是从内向外伸长的,所有,闭眼的动作就相当于慢慢减小分割线的高度,即可实现眼睛慢慢缩小的效果
**过程:**大眼慢慢闭上眼睛(分割线缩小至0),然后开始 呼吸
<div class="eyeSocket eyeSocketSleeping" id="bigEye">
<div id="eyeball"></div>
</div>
let bigEye = document.getElementById('bigEye'); // 获取元素
let rotTimer; // 定时器
let leftRotSize = 0; // 旋转角度
let ballSize = 0; // 眼睛尺寸
function getEyeballChart() {
eyeballChart.setOption({
series: [
{
//其他代码
startAngle: `${0 + leftRotSize * 5}`, // 起始角度
endAngle: `${270 + leftRotSize * 5}`, // 结束角度
//其他代码
splitLine: {
show: true,
length: ballSize, // 分割线长度
//其他代码
},
axisLabel: false
},
{
//其他代码
startAngle: `${45 + leftRotSize * 5}`,//倾斜角度
endAngle: `${315 + leftRotSize * 5}`,
//其他代码
splitLine: {
show: true,
length: ballSize,
//其他代码
},
axisLabel: false
}
]
});
}
// 休眠
function toSleep() {
isSleep = true;
clearInterval(rotTimer); // 清除定时器
rotTimer = setInterval(() => {
getEyeballChart()
if (ballSize > 0) {
ballSize -= 0.1; // 当眼球存在时慢慢减小
} else {
bigEye.className = 'eyeSocket eyeSocketSleeping'; // 眼球消失后添加呼吸
}
leftRotSize === 360 ? (leftRotSize = 0) : (leftRotSize += 0.1); // 旋转,
}, 10);
}
旋转的实现原理:(小生去学习了原作者另一篇关于动效的文章,看了一下还是蛮简单)
「 修改每个圆的起始角度(startAngle)和结束角度(endAngle),并不断的刷新视图」
「 增加度数为逆时针旋转,减去度数为顺时针旋转」
如此以来就实现了眼睛缩小消失,然后开始呼吸过程,同时咱们的大眼也进入了生命仪式之「休眠状态」
✨生命仪式: 起床气状态
小生很赞同心理学上的说法: 情绪会让你接近生命的本真。
生命不就是情绪的结合嘛?没有情绪怎么能被称为生命呢?
在设置起床气的状态之前,我还需要做点准备工作,让大眼处于休眠状态
通过修改类名
<div class="eyeSocket eyeSocketSleeping" id="bigEye">
<div id="eyeball"></div>
</div>
更改眼球的尺寸
let ballSize = 0; // 眼睛尺寸
唤醒
为了能够给唤醒大眼,小生需要准备一个唤醒动作——点击事件
let isSleep = true; // 是否处于休眠状态
// 点击
bigEye.addEventListener('click', () => {
//判断是否休眠
if (!isSleep) return;
//执行唤醒
clickToWakeup();
})
// 唤醒
function clickToWakeup() {
isSleep = false;
eyeFilter.className = bigEye.className = 'eyeSocket eyeSocketLookging';
clearInterval(rotTimer);
rotTimer = setInterval(() => {
getEyeballChart()
ballSize <= 50 && (ballSize += 1);
leftRotSize === 360 ? (leftRotSize = 0) : (leftRotSize += 0.5);
}, 10);
}
这样,小生点击一下,大眼就苏醒过来了
生气
但是!
这是一个没有情绪的大眼,而且小生想要的是一个有起床气的大眼所以这样的大眼咱们不要!
退!退!退!退!退!
···
我们只需要修改一下唤醒他之后的操作,给他添加上起床气就行了。
接着来吧
首先,小生先把大眼的蓝色抽离出来,使用CSS变量代替,然后再苏醒后给他添加代表生气的红色就行了
body {
/*其他属性*/
perspective: 1000px;
--c-eyeSocket: rgb(41, 104, 217);
--c-eyeSocket-outer: #02ffff;
--c-eyeSocket-outer-shadow: transparent;
--c-eyeSocket-inner: rgb(35, 22, 140);
}
.eyeSocket {
/*其他属性*/
border: 4px solid var(--c-eyeSocket);
box-shadow: 0px 0px 50px var(--c-eyeSocket-outer-shadow);
transition: border .5s ease-in-out, box-shadow .5 ease-in-out;
z-index: 1;
}
.eyeSocket::before,
.eyeSocket::after {
/*其他属性*/
transition: all .5s ease-in-out;
}
.eyeSocket::before {
/*其他属性*/
border: 6px solid var(--c-eyeSocket-outer);
}
.eyeSocket::after {
/*其他属性*/
border: 4px solid var(--c-eyeSocket-inner);
box-shadow: inset 0px 0px 30px var(--c-eyeSocket-outer-shadow);
}
JS
function getEyeballChart() {
eyeballChart.setOption({
series: [
{
//其他代码
splitLine: {
//其他代码
lineStyle: {
//其他代码
shadowColor:ballColor,
color: ballColor, // 分割线颜色
}
},
axisLabel: false
},
{
//其他代码
axisLine: {
//其他代码
},
//其他代码
splitLine: {
//其他代码
lineStyle: {
//其他代码
shadowColor: ballColor, // 阴影颜色
//其他代码
color: ballColor, // 分割线颜色
}
},
//其他代码
}
]
});
}
//样式
//生气模式
function 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)');
ballColor = 'rgb(208,14,74)';
}
// 常态模式
function 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)');
ballColor = 'rgb(0,238,255)';
}
//唤醒函数
function clickToWakeup() {
//其他代码
setAngry(); // 设置为生气模式
//其他代码
rotTimer = setInterval(() => {
//其他代码
}, 10);
}
大眼生气长这个样子:
更生气
不知道在座(站?蹲?)各位是如何看待,但是对小生来说,感觉这样大眼还是不够生气。
没错还不够生气
说干就干
小生这里是模仿了大佬的svg滤镜的方法,svg滤镜的属性和使用方法非常的多,小生用的也不是很娴熟,小生在本本中就不再赘述了,网上教程很多。
小生见大佬用的是feTurbulence来形成噪声,然后用feDisplacementMap替换,来给大眼添加粒子效果,但feDisplacementMap会混合掉元素,所以小生需要一个大眼的替身来代替大眼被融合。
创建大眼替身
<div class="filter">
<div class="eyeSocket" id="eyeFilter"></div>
</div>
CSS
.filter {
width: 100%;
height: 100%;
filter: url('#filter');
}
.eyeSocket,
.filter .eyeSocket {
position: absolute;
left: calc(50% - 75px);
top: calc(50% - 75px);
width: 150px;
/* 长宽比 1:1 如果浏览器不支持该属性,换成 height: 150px 也一样 */
aspect-ratio: 1;
border-radius: 50%;
border: 4px solid var(--c-eyeSocket);
box-shadow: 0px 0px 50px var(--c-eyeSocket-outer-shadow);
transition: border .5s ease-in-out, box-shadow .5 ease-in-out;
z-index: 1;
}
.filter .eyeSocket {
opacity: 0;
left: calc(50% - 92px);
top: calc(50% - 92px);
transition: all 0.5s ease-in-out;
}
融合
HTML
<div class="filter">
<div class="eyeSocket" id="eyeFilter"></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>
芜湖,果然献祭了一只大眼,成品的效果还不错。
看起来酷炫多了
邪王真眼!
接下来将这个粒子光辉和大眼本体对齐
额···
本质上是因为feDisplacementMap设置了scale属性的原因。
feDisplacementMap其实就是一个位置替换滤镜,通过就是改变元素和图形的像素位置的进行重新映射,然后替换一个新的位置,形成一个新的图形。
而scale就是替换公式计算后偏移值相乘的比例,影响着图形的偏移量和呈现的效果。
但是话虽如此,咱这个光环不能真的就这么戴着呀,咱们还需要对光环的位置进行一些微调。
left: calc(50% - 92px);
top: calc(50% - 92px);
ENMMMM····
这下光环也有了,看起来确实比之前生气多了。
但是我还需要对大眼做一些调整,因为大眼在常规状态并不需要这个光环,睡着的时候这光环在旁边“滋啦滋啦”的,很吵的慌,所以我们需要把在常态下把光环去掉。
小生用的是opacity属性来控制,当大眼处于生气状态时,光环为不透光,处于常规状态时,光环透明不可见。
CSS
opacity: 0;
left: calc(50% - 92px);
top: calc(50% - 92px);
transition: all 0.5s ease-in-out;
JS
function clickToWakeup() {
//其他代码
eyeFilter.style.opacity = '1';
//其他代码
}
这样设置完成,一个更生气的大眼就完成了
EMMMMMMMMMMMMMMMMMMMM···
《好像还是不够》
更更生气邪王真眼!
不知道各位觉得如何,但是小生认为,一个真正生气的大眼,不止局限于自己生气,还要找别人发泄!!
所以小生给大眼添加了找人的动效
当然他是找不到的,他那么笨
其实就是通过大眼的左右旋转,通过CSS来实现。
CSS
body{
perspective:1000px
}
.eyeSocketLookging {
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);
}
}
JS
let eyeFilter = document.getElementById('eyeFilter')
// 唤醒
function clickToWeakup() {
//其他代码
eyeFilter.className = bigEye.className = 'eyeSocket eyeSocketLookging';
//其他代码
rotTimer = setInterval(() => {
//其他代码
}, 10);
}
// 点击
bigEye.addEventListener('click', () => {
if (!isSleep) return;
// console.log('a');
clickToWeakup();
})
向左看时,Y轴的偏移量为-70px,同时按Y轴旋转-30°
向右看时,Y轴的偏移量为70px,同时按Y轴旋转30°
✨ 生命仪式:自我调整状态
这个状态非常好解释,大眼虽然有起床气,但是也仅局限于起床对吧,总不能一直让他生气,气坏了可咋办 重写一个 ,
带着情绪工作,效果也不好,不是吗?
所以我们还需要给他点时间,让他调整一下自我状态,恢复正常。
这个自我调整状态就一个从生气变为常态的过程。
在这个过程,大眼需要将代表生气的红色变更为常态的蓝色,同时红眼也会慢慢的褪去。
但这个自我调整状态还是属于唤醒状态中,只是需要放在起床气之后。
思路
1、退出起床气的状态。
2、变回常态
为了保证这两个步骤的先后顺序,可以使用Promise,不懂Promise的同学可以去学习一下。小生也说不清楚。
// 监听动画结束
bigEye.addEventListener('webkitAnimationEnd', () => {
console.log('aa');
new Promise(res => {
clearInterval(rotTimer);
rotTimer = setInterval(() => {
getEyeballChart();//更新视图
ballSize > 0 && (ballSize -= 0.5);
leftRotSize === 360 ? (leftRotSize = 0) : (leftRotSize += 0.1);
if (ballSize === 0) {
clearInterval(rotTimer);
res();
}
}, 10);
}).then(() => {
eyeFilter.style.opacity = '0';
eyeFilter.className = bigEye.className = 'eyeSocket';
setNormal();//设置常态
document.body.addEventListener('mousemove', focusOnMouse);
rotTimer = setInterval(() => {
getEyeballChart();
ballSize <= 12 && (ballSize += 0.1);
leftRotSize === 360 ? (leftRotSize = 0) : (leftRotSize += 0.1);
}, 10);
});
});
添加了这样一个监听事件后,咱们的大眼就已经具备了自我调整的能力了:
✨ 生命仪式:工作状态
接下就是大眼的重中之重,也就是大眼的工作状态
小生给大眼的工作特别简单,就是盯着我的鼠标指针。
盯着,不只是说说而已,要如何才能表现出大眼已经盯住了呢?
小生的思路是;
1、以大眼的位置为原点建立一个直角坐标系
2、然后通过监听鼠标移动事件,获取鼠标所在位置,计算出鼠标处于大眼坐标系的坐标。
3、将整个视口背景以X轴和Y轴进行等分成无数个旋转角度,通过鼠标坐标的数值和正负来调整大眼眼框和眼睛的Y轴和Z轴旋转,从而达到盯住鼠标的目的。
好,接下来理清思路,我们付诸行动
//工作
function focusOnMouse(e) {
// 视口大小,获取整个视口大小
let clientWidth = document.body.clientWidth;
let clientHeight = document.body.clientHeight;
// 建立原点 以大眼为原点
let origin = [clientWidth / 2, clientHeight / 2];
// 鼠标坐标
let mouseCoords = [e.clientX - origin[0], origin[1] - e.clientY];
// 旋转角度
let eyeXDeg = mouseCoords[1] / clientHeight * 80;
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)`;
//设置休眠
if (sleepTimer) clearTimeout(sleepTimer);
sleepTimer = setTimeout(() => {
toSleep();
}, 30000);
}
注意: 如果觉得旋转角度不够大,可以调整代码中的80和60,最大可以到180,也就是完全朝向,但是由于大眼终归是一个平面生物,如果旋转度数过大,就很容易穿帮,如果旋转角度为180,大眼就会在某个方向完全消失看不见(因为大眼没有厚度,这个也许是可以优化的点),所以个人喜好调整吧。
✨ 生命仪式:懒惰状态
顾名思义,懒惰状态就是···懒惰状态。
小生在给大眼设计的懒惰状态就是当在下的鼠标超过30秒没有移动时,大眼就会进入休眠状态。
所以生命仪式的最后收尾其实非常的轻松,没有大量的代码,只需要添加一个定时器,然后修改休眠状态的代码,将大眼的所有参数初始化即可。
let sleepTimer;//休眠定时器
// 休眠
function toSleep() {
//其他操作
document.body.removeEventListener('mousemove', focusOnMouse);
bigEye.style.transform = `rotateY(0deg) rotateX(0deg)`;
eyeball.style.transform = `translate(0px, 0px)`;
}
//工作
function focusOnMouse(e) {
//其他操作
//设置休眠
if (sleepTimer) clearTimeout(sleepTimer);
sleepTimer = setTimeout(() => {
toSleep();
}, 30000);
}