PK创意闹新春,我正在参加「春节创意投稿大赛」,详情请看:春节创意投稿大赛
介绍
春节将至,记得小时候过年是无忧无虑的,大人们会买很多玩具给我们玩,其中有种最为怪异——布老虎,说是布老虎感觉又像年兽,又想奥特曼的,对了还有一种玩具大鲨鱼咬手指的那个不知道还记得么跟小伙伴一起玩谁被咬到谁就要受到惩罚,今年正好又是虎年,那么我们本期就用css+js来将两种玩具融合起来,做一只会咬手指的丑萌丑萌的布老虎玩具。
最后完成效果是如何呢?我们先来一睹为快吧:
感觉是不是有点特别呢,我们接下来会分成scss绘制布老虎各部分,以及纯js完成咬手逻辑,其中你可以了解到比如,box-shadow,boder-radius,animation,还有animationend监听等一些前端小知识。准备好了么,我们要出发了~
正文
1.HTML结构
<div class="tiger">
<div class="tiger-beard"></div>
<div class="tiger-ear"></div>
<div class="tiger-head">
<div class="tiger-eye left"><span></span></div>
<div class="tiger-eye right"><span></span></div>
<div class="tiger-nose"><span></span><span></span><span></span></div>
<div class="tiger-mouth">
<div>
<div class="tooth">
<span></span>
<span></span>
<span></span>
<span></span>
</div>
<div class="tooth down">
<span></span>
<span></span>
<span></span>
<span></span>
</div>
</div>
</div>
</div>
</div>
我们先来介绍每一个部分是做什么的:
-
div.tiger:布老虎主容器
-
div.tiger-beard:脸部周围绒毛团团
-
div.tiger-ear:在里面将会用伪元素做对来回摇摆的耳朵
-
div.tiger-head:脸部容器,至于鼻子,眼睛,嘴巴等等都会在这里完成
2.绘制轮廓
$blue: rgb(58, 106, 179);
$red: rgb(158, 14, 24);
$yellow: rgb(255, 186, 13);
$tomato: rgb(194, 39, 54);
.tiger {
position: relative;
width: 300px;
height: 300px;
.tiger-head {
position: relative;
width: 100%;
height: 85%;
border-radius: 50% 50% 30% 30%;
background-color: $blue;
&::before {
content: "";
display: block;
width: 320px;
height: 120px;
background-color: $yellow;
position: absolute;
bottom: 0px;
left: 50%;
margin-left: -160px;
border-radius: 100px;
}
}
}
在此之前我们先定义四个主色值,然后将布老虎主容器就定义为300*300吧。至于div.tiger-head填充成蓝色,再用伪元素before填充为黄色来制成布老虎的下颌。
3.绘制耳朵
.tiger-ear {
position: absolute;
width: 100%;
top: -30px;
&::before {
content: "";
left: 10px;
display: block;
position: absolute;
width: 140px;
height: 160px;
border-radius: 0 100% 0 100%;
background-color: $yellow;
transform: rotate(10deg);
animation: ear-left-move .9s infinite alternate linear;
}
&::after {
content: "";
right: 10px;
display: block;
position: absolute;
width: 140px;
height: 160px;
border-radius: 100% 0 100% 0;
background-color: $yellow;
transform: rotate(-10deg);
animation: ear-right-move .9s infinite alternate linear;
}
@keyframes ear-left-move {
0%{
transform: rotate(5deg);
}
100%{
transform: rotate(15deg);
}
}
@keyframes ear-right-move {
0%{
transform: rotate(-15deg);
}
100%{
transform: rotate(-5deg);
}
}
}
两只耳朵么,我们可以把它想象出两个花瓣形状,这个直接用border-radius: 0 100% 0 100%就可以绘制出来他,
再通过css3的animation,做一个来回摇摆的动画(即改变rotate)就差不多了。
4.绘制绒毛
.tiger-beard {
position: absolute;
border-radius: 50%;
width: 110px;
height: 110px;
background-color: transparent;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
box-shadow: -120px 0px 0 $blue, 120px 0px 0 $blue, 60px 70px 0 $blue, -60px 70px 0 $blue, 0px 80px 0 $blue;
}
说是绒毛其实是五个蓝色圆圈摆放到脸周围,可能新手的小伙伴就会在下面放五个div去做定位了,但是这里给大家介绍一个新方法,如上面代码用box-shadow,可以做出圆球的影子前面的x和y控制其坐标位置。
5.绘制眼睛
.tiger-eye {
width: 110px;
height: 110px;
border-radius: 50%;
background-color: $red;
position: absolute;
top: 28px;
display: flex;
align-items: center;
justify-content: center;
transition: .15s transform ease-in;
&.big{
&>span{
transform: scale(1.05);
}
}
& > span {
display: block;
width: 80px;
height: 80px;
box-sizing: border-box;
border-radius: 50%;
border: 10px solid white;
display: flex;
align-items: center;
justify-content: center;
&::after {
content: "";
display: block;
background-color: black;
border-radius: 50%;
width: 30px;
height: 30px;
}
}
&.left {
left: -5px;
border-radius: 0% 50% 50% 50%;
}
&.right {
right: -5px;
border-radius: 50% 0% 50% 50%;
}
}
眼睛绘制比较简单,其实就是套圆,这里不做过多赘述了,提前要说的是加了一个big类,为了后面触发咬合动画时做准备,因为咬合的时候眼睛会瞪大一点。
6.绘制鼻子
.tiger-nose {
position: absolute;
bottom: 100px;
left: 50%;
width: 70px;
height: 190px;
margin-left: -35px;
background-color: $red;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border-radius: 100% 100% 0 0;
span {
width: 30px;
height: 30px;
box-sizing: border-box;
display: block;
background-color: white;
border: 10px solid $blue;
border-radius: 50%;
margin: 15% 0;
position: relative;
}
&::before {
content: "";
display: block;
position: absolute;
top: 0;
background-color: $red;
width: 118px;
height: 118px;
border-radius: 0 100% 0 100%;
transform: rotate(45deg);
}
}
鼻子是由红矩形做主体加上类似花瓣的上半部分的伪元素构成,为了不显单调,又加了三个圆点装饰。我们用弹性布局,修改flex-direction为column就可以让其圆点装饰有规律的垂直分布起来。
7.绘制嘴巴
.tiger-mouth {
position: absolute;
width: 270px;
height: 80px;
left: 50%;
margin-left: -135px;
border-radius: 15px;
bottom: 20px;
background-color: $red;
display: flex;
align-items: center;
justify-content: center;
& > div {
position: relative;
height: 50px;
width: 85%;
background-color: $tomato;
border-radius: 50px;
overflow: hidden;
}
}
嘴巴很简单。我们绘制了一个圆角矩形和椭圆去完成,再用flex去做居中定位。
.tooth {
position: absolute;
top: 50%;
margin-top: -30px;
left: 20px;
right: 20px;
display: flex;
justify-content: center;
span {
position: relative;
display: block;
width: 0;
height: 0;
margin: 0 5px;
border-width: 20px 15px;
border-style: solid;
border-color: white transparent transparent transparent;
cursor: pointer;
transition: .2s transform;
&.active{
transform: translateY(-10px);
}
}
&.down {
transform: scaleY(-1);
margin-top: -10px;
}
}
接下来,我们再说说牙齿,牙齿就是一个个白色小三角形,这里绘制的办法也炒鸡简单,如上代码的border部分就可以完美实现一个三角。另外我们要做两排牙齿,这里下牙齿想偷个懒就用,不再做一份了,再介绍大家一个小技巧,就是用把上牙Y轴翻转过来(即transform: scaleY(-1)),就可以实现,当然还要微调下距离。至于再加一个激活类active,这个为了后面我们按下牙齿的js逻辑服务,就是让他在Y轴往下收缩。
8.牙齿收回
let $tooth = document.querySelectorAll(".tooth span"); // 获取牙齿
let $mouth = document.querySelector(".tiger-mouth>div"); // 获取嘴巴
let $eye = document.querySelectorAll(".tiger-eye"); // 获取眼睛
let activeIndex = -1; // 牙齿机关触发值
let tigerState = "" // 触发状态
// 执行初始化事件
function initTiger() {
activeIndex = getRandom($tooth);
tigerState = "normal"
}
initTiger()
// 获取牙齿机关触发随机值
function getRandom(list) {
return ~~(list.length * Math.random());
}
// 咬合动作
function handleBite() {}
// 绑定牙齿触发事件
$tooth.forEach((el, index) => {
el.addEventListener("click", e => {
const { target } = e;
if (target.classList.value == "active" || tigerState == "active") return;
if (activeIndex == index) return handleBite()
target.classList.add('active')
})
})
这里的业务逻辑很简单,大体说一下吧,就是一开始要初始化当前的状态tigerState为normal,后面改变为active表明为触发使其不能再点击牙齿按钮。当然我们还要随机获得一个activeIndex作为我们牙齿机关的触发值,当按下的牙齿的下标跟触发值一样时,就会触发咬合动作,表明一局的结束。当然如果没触发到机关,我们按下一个牙齿就会给牙齿附上css的active类让其陷下,表明安全。
9.咬合动作
.tiger-mouth {
& > div {
&.bite{
animation: bite-mouth .75s ease-in;
span{
animation: bite-tooth .85s ease-in;
}
}
@keyframes bite-mouth {
0%,100%{
height: 50px;
}
50%{
height: 10px;
}
}
@keyframes bite-tooth {
0%,100%{
top: 0;
}
50%{
top: 10px;
}
}
}
}
// 咬合动作
function handleBite() {
$tooth.forEach(el => el.classList.remove('active')) // 恢复牙齿
$eye.forEach(el=>el.classList.add("big")) // 眼睛瞪大
tigerState = "active"; // 改变状态
$mouth.classList.add("bite") // 添加咬合动画
}
这里的咬合动画我们主要是通过css3的animation去完成同时我们要马上恢复所按下的牙齿并且让眼睛瞪大,然后要写好动画(即嘴唇和牙齿的上下变化),当触发咬合事件就会附上bite自动执行一次,当然我们还要把布老虎的状态变为激活(即tigerState->"active"),此时牙齿是不会让你去点击的。当然,还有个方案可以不用写js就可以实现。比如说在用css的bite上加pointer-events: none,也可以轻松完成这个业务。
最后我们要重复利用保证下一回合,牙齿恢复初始状态,这时我们要监听刚才咬合动画的结束,让其初始化。
$mouth.addEventListener("animationend", () => {
$mouth.classList.remove("bite") // 移除咬合动画
$eye.forEach(el=>el.classList.remove("big"))
initTiger();
})
这里要说的是,监听这个动画结束,这里使用的是animationend做监听,animationend 事件是一个监听CSS 动画触发的事件,他可以精准的捕捉到你css3动画何时结束的,不必再使用setTimeout定时器去触发了。但是也缺点也是有的,就是兼容性并不是那么好,我们这里仅在谷歌上做效果展示,如果小伙伴们要兼容性更好还是要用定时器去判断。
结语
讲到这里我们就已经大功告成了,不知道,这只丑萌丑萌的布老虎玩具是否把你拉回到童年时光。
关于这个动画【Cloth Tiger】,在我的codepen上就可以找到关于它演示和代码,有兴趣的小伙伴可以康康哟。
本期还是比较侧重基础和动画创意的,主要是新手向,大佬勿喷,经常用css和js写写动画比较有趣,毕竟天天写业务代码实在太无聊了,这些不仅可以熟悉css和js的基本功,而且会迸发出更多的创意来,也是一种锻炼自己的学习方式吧,多多练习,一起进步,最后祝大家虎年大吉,万事遂意~