HarmonyOS - 纯CSS实现吹灭蜡烛动画 原创 精华
作者:炒栗子
前言
前段时间HarmonyOS 3发布了,吸引了不少的眼球,为了体验鸿蒙应用开发,决定动手实现一个案例——通过css动画实现吹灭蜡烛动画,看了一下鸿蒙应用开发文档,有js和ets两种开发方式,综合考量了一下,决定采用js方式实现。
效果展示
实现思路
-
通过变换translate()的X轴位置,实现最右边蜡烛眼睛移动效果;
-
通过改变height、width和left属性实现最右边蜡烛的嘴巴吹起动画;
-
通过scale()和translate()动画函数实现最右侧蜡烛吹起过程的身体变化;
-
通过改变left属性实现最左侧蜡烛的水平方向的烟雾;
-
通过改变left和top属性实现最左侧蜡烛的竖直方向的烟雾;
-
通过变换translate()的Y轴方向、scale()、background-color、box-shadow和border-color实现蜡烛被吹灭时左移效果和蜡烛摩擦生火体色的变化;
-
通过translate()和rotateZ()函数实现蜡烛被吹灭时的左右抖动以及烛芯摩擦生火动画;
-
通过border透明度、translate()和scale()实现烛光点燃时产生的光晕;
-
通过backgroun-color、left、width和height实现火焰闪烁效果,通过translate-origin、rotate()和translate()实现眨眼效果;
-
通过background-color变化实现背景明暗变化。
组件实现
1. hml代码
分为三部分:最左边的蜡烛、最右边的蜡烛和底座;最右边蜡烛由烛芯、火焰、眼睛、嘴巴和烛体构成,最左边蜡烛由烛芯、眼睛、烛体、火焰和烟雾组成。
<div class="page">
<div class="wrapper">
<div class="candles">
<!-- 背景光 -->
<div class="light_wave"></div>
<!-- 最右边蜡烛 -->
<div class="candle1">
<div class="candle1_body">
<div class="candle1_eyes">
<div class="candle1_eyes-left"></div>
<div class="candle1_eyes-right"></div>
</div>
<div class="candle1_mouth"></div>
</div>
<div class="candle1_stick"></div>
</div>
<!-- 最左边蜡烛 -->
<div class="candle2">
<div class="candle2_body">
<div class="candle2_eyes">
<div class="candle2_eyes-left"></div>
<div class="candle2_eyes-right"></div>
</div>
</div>
<div class="candle2_stick"></div>
<div class="candle2_fire"></div>
</div>
<!-- 烟雾 -->
<div class="candle_smoke1"></div>
<div class="candle_smoke2"></div>
</div>
<!-- 底座 -->
<div class="floor"></div>
</div>
</div>
2. css代码
实现了最右边蜡烛眼睛向左看、嘴巴对最左边蜡烛的火焰吹气的动画和吸气形体变化的动画;
实现了最左边蜡烛的火焰闪烁、火焰被吹灭时产生的烟雾动效、烛芯扰动和蜡烛向左偏移的效果,蜡烛摩擦生火的效果。
.page {
height: 100%;
width: 100%;
background-color: #FFF;
animation: change-background 3s infinite linear;
}
.floor {
position: absolute;
left: 50%;
top: 50%;
width: 100%;
height: 9px;
margin: 0px 80px;
background-color: #673C63;
transform: translate(-50%, -50%);
box-shadow: 0px 2px 5px #111;
z-index: 2;
}
.candles {
position: absolute;
left: 50%;
top: 50%;
width: 250px;
height: 150px;
transform: translate(-50%, -100%);
z-index: 1;
}
.candle1 {
flex-direction: column;
position: absolute;
left: 50%;
top: 50%;
z-index: 100;
width: 35px;
height: 100px;
background-color: #ffffff;
border: 3px solid #673C63;
border-bottom: 0px;
border-radius: 3px;
transform-origin: right center;
transform: translate(60%, -25%);
box-shadow: -2px 0px 0px #95c6f2 inset;
animation: expand-body 3s infinite linear;
}
.candle1_body {
flex-direction: column;
}
.candle1_stick, .candle2_stick {
position: absolute;
left: 50%;
top: 0%;
width: 3px;
height: 15px;
background-color: #673C63;
border-radius: 8px;
transform: translate(-50%, -100%);
}
.candle2_stick {
height: 12px;
transform-origin: center bottom;
animation: stick-animation 3s infinite linear;
}
.candle1_eyes, .candle2_eyes {
position: absolute;
z-index: 1000;
left: 50%;
top: 0%;
width: 35px;
height: 30px;
transform: translate(-50%, 0%);
}
.candle1_eyes-left {
position: absolute;
z-index: 1000;
left: 40%;
top: 20%;
width: 5px;
height: 5px;
border-radius: 2px;
background-color: #673C63;
transform: translate(-70%, 0%);
animation: blink-eyes 3s infinite linear;
}
.candle1_eyes-right {
position: absolute;
z-index: 1000;
left: 80%;
top: 20%;
width: 5px;
height: 5px;
border-radius: 5px;
background-color: #673C63;
transform: translate(-70%, 0%);
animation: blink-eyes 3s infinite linear;
}
.candle1_mouth {
position: absolute;
left: 40%;
top: 20%;
width: 0px;
height: 0px;
border-radius: 20px;
background-color: #673C63;
transform: translate(-50%, -50%);
animation: uff 3s infinite linear;
}
.candle_smoke1 {
position: absolute;
left: 30%;
top: 50%;
width: 30px;
height: 3px;
background-color: grey;
transform: translate(-50%, -50%);
animation: move-left 3s infinite linear;
}
.candle_smoke2 {
position: absolute;
left: 30%;
top: 40%;
width: 10px;
height: 10px;
border-radius: 10px;
background-color: grey;
transform: translate(-50%, -50%);
animation: move-top 3s infinite linear;
}
.candle2 {
position: absolute;
left: 20%;
top: 65%;
z-index: 100;
width: 42px;
height: 60px;
background-color: #FFFFFF;
border: 3px solid #673C63;
border-bottom: 0px;
border-radius: 3px;
transform: translate(60%, -15%);
transform-origin: right center;
box-shadow: -2px 0px 0px #95c6f2 inset;
animation: shake-left 3s infinite linear;
}
.candle2_eyes {
flex-direction: column;
}
.candle2_eyes-left {
position: absolute;
left: 40%;
top: 50%;
width: 5px;
height: 5px;
display: flex;
border: 0px solid #673C63;
border-radius: 5px;
background-color: #673C63;
transform: translate(-80%, 0%);
animation: changeto-lower 3s infinite linear;
}
.candle2_eyes-right {
position: absolute;
left: 80%;
top: 50%;
width: 5px;
height: 5px;
display: flex;
border: 0px solid #673C63;
border-radius: 5px;
background-color: #673C63;
transform: translate(-80%, 0%);
animation: changeto-greater 3s infinite linear;
}
.light_wave {
flex-direction: column;
position: absolute;
top: 50%;
left: 50%;
width: 75px;
height: 75px;
border-radius: 75px;
z-index: 0;
transform: translate(0%, 70%) scale(2.5, 2.5);
border: 2px solid #FFFFFF;
opacity: 0.2;
animation: expand-light 3s infinite linear;
}
.candle2_fire {
position: absolute;
z-index: 1000;
top: -20px;
left: 50%;
display: flex;
width: 16px;
height: 20px;
background-color: red;
border-radius: 20px;
background-color: #ff9800;
transform: translate(-40%, -50%);
animation: dance-fire 3s infinite linear;
}
/* 最右侧蜡烛眼睛闪烁 */
@keyframes blink-eyes {
0% {
opacity: 1;
transform: translate(-70%, 0%);
}
35% {
opacity: 1;
transform: translate(-70%, 0%);
}
36% {
opacity: 0;
transform: translate(-70%, 0%);
}
39% {
opacity: 0;
transform: translate(-70%, 0%);
}
40% {
opacity: 1;
transform: translate(-70%, 0%);
}
50% {
transform: translate(-140%, 0%);
}
65% {
transform: translate(-140%, 0%);
}
66% {
transform: translate(-70%, 0%);
}
}
/* 最右侧蜡烛吸气形体变化 */
@keyframes expand-body {
0% {
transform: scale(1, 1) translate(60%, 0%);
}
40% {
transform: scale(1, 1) translate(60%, 0%);
}
45% {
transform: scale(1.1, 1.1) translate(60%, 0%);
}
55% {
transform: scale(1.1, 1.1) translate(60%, 0%);
}
60% {
transform: scale(0.89, 0.89) translate(60%, 0%);
}
65% {
transform: scale(1, 1) translate(60%, 0%);
}
70% {
transform: scale(0.95, 0.95) translate(60%, 0%);
}
75% {
transform: scale(1, 1) translate(60%, 0%);
}
}
/* 最右侧蜡烛口型变化 */
@keyframes uff {
0% {
width: 0px;
height: 0px;
}
40% {
width: 0px;
height: 0px;
}
50% {
width: 15px;
height: 15px;
left: 30%;
}
54% {
width: 15px;
height: 15px;
left: 30%;
}
59% {
width: 5px;
height: 5px;
left: 20%;
}
62% {
width: 2px;
height: 2px;
left: 20%;
}
67% {
width: 0px;
height: 0px;
left: 30%;
}
}
/* 光影背景变化 */
@keyframes change-background {
0% {
background-color: #fff;
}
59% {
background-color: #fff;
}
61% {
background-color: #000;
}
97% {
background-color: #000;
}
98% {
background-color: #fff;
}
100% {
background-color: #fff;
}
}
/* 最左侧蜡烛熄灭的水平方向烟雾 */
@keyframes move-left {
0% {
width: 0px;
left: 40%;
}
59% {
width: 0px;
left: 40%;
}
60% {
width: 30px;
left: 30%;
}
68% {
width: 0px;
left: 20%;
}
100% {
width: 0px;
left: 40%;
}
}
/* 最左侧蜡烛熄灭的竖直方向的烟雾 */
@keyframes move-top {
0% {
width: 0px;
height: 0px;
top: 0%;
}
64% {
width: 0px;
height: 0px;
top: 0%;
}
65% {
width: 10px;
height: 10px;
top: 40%;
left: 40%;
}
80% {
width: 0px;
height: 0px;
top: 20%;
}
100% {
width: 0px;
height: 0px;
top: 0%;
}
}
/* 最左侧蜡烛熄灭的向左偏移及蜡烛摩擦生火体色的变化 */
@keyframes shake-left {
0% {
left: 20%;
background-color: #FFFFFF;
border: 3px solid #673C63;
border-bottom: 0px;
border-radius: 3px;
}
40% {
left: 20%;
background-color: #FFFFFF;
border: 3px solid #673C63;
border-bottom: 0px;
border-radius: 3px;
}
50% {
left: 20%;
transform: translate(60%, 5%);
}
54% {
left: 20%;
transform: translate(60%, 5%);
}
59% {
left: 20%;
transform: translate(60%, 5%);
}
62% {
left: 18%;
transform: translate(60%, 5%);
}
67% {
left: 20%;
transform: translate(60%, 5%);
}
75% {
left: 20%;
transform: scale(1.15, 0.85) translate(60%, 5%);
background-color: #fff;
border-color: #673C63;
}
91% {
left: 20%;
transform: scale(1.18, 0.82) translate(60%, 5%);
background-color: #f44336;
border-color: #f44336;
box-shadow: -2px 0px 0px #f44336 inset;
}
92% {
left: 20%;
transform: scale(0.85, 0.95) translate(60%, 5%);
}
95% {
left: 20%;
transform: scale(1.05, 0.95) translate(60%, 5%);
}
97% {
left: 20%;
transform: scale(1, 1) translate(60%, 5%);
}
}
/* 最左侧蜡烛熄灭时烛芯扰动和摩擦生火 */
@keyframes stick-animation {
0% {
left: 50%;
top: 0%;
transform: translate(-50%, 0%);
}
40% {
left: 50%;
top: 0%;
transform: translate(-50%, 0%);
}
50% {
left: 50%;
top: 0%;
transform: translate(-50%, 0%);
}
54% {
left: 50%;
top: 0%;
transform: translate(-50%, 0%);
}
59% {
left: 50%;
top: 0%;
transform: translate(-50%, 0%);
}
62% {
left: 50%;
top: 0%;
transform: rotateZ(-15deg) translate(-50%, 0%);
}
65% {
left: 50%;
top: 0%;
transform: rotateZ(15deg) translate(-50%, 0%);
}
70% {
left: 50%;
top: 0%;
transform: rotateZ(-5deg) translate(-50%, 0%);
}
72% {
left: 50%;
top: 0%;
transform: rotateZ(5deg) translate(-50%, 0%);
}
74% {
left: 50%;
top: 0%;
transform: rotateZ(0deg) translate(-50%, 0%);
}
84% {
left: 50%;
top: 0%;
transform: rotateZ(0deg) translate(-50%, 0%);
}
85% {
transform: rotateZ(180deg) translate(-50%, 0%);
}
92% {
left: 50%;
top: 0%;
transform: rotateZ(0deg) translate(-50%, 0%);
}
93% {
left: 50%;
top: 0%;
transform: rotateZ(0deg) translate(-50%, 0%);
}
}
/* 最左侧蜡烛火焰光圈变化 */
@keyframes expand-light {
0% {
transform: translate(-25%, -50%) scale(2.5, 2.5);
border: 2px solid #FFFFFF;
opacity: 0.2;
}
10% {
transform: translate(-25%, -50%) scale(0, 0);
border: 2px solid #FFFFFF;
opacity: 0;
}
20% {
transform: translate(-25%, -50%) scale(1, 1);
}
26% {
transform: translate(-25%, -50%) scale(2, 2);
border: 2px solid #FFFFFF;
opacity: 0.5;
}
27% {
transform: translate(-25%, -50%) scale(2, 2);
border: 2px solid #FFFFFF;
opacity: 0.5;
}
28% {
transform: translate(-25%, -50%) scale(2.5, 2.5);
border: 2px solid #FFFFFF;
opacity: 0.2;
}
29% {
transform: translate(-25%, -50%) scale(0, 0);
border: 2px solid #FFFFFF;
opacity: 0;
}
58% {
transform: translate(-25%, -50%) scale(2.5, 2.5);
border: 2px solid #FFFFFF;
opacity: 0.2;
}
50% {
transform: translate(-25%, -50%) scale(1, 1);
}
56% {
transform: translate(-25%, -50%) scale(2, 2);
border: 2px solid #FFFFFF;
opacity: 0.5;
}
57% {
transform: translate(-25%, -50%) scale(2, 2);
border: 2px solid #FFFFFF;
opacity: 0.5;
}
59% {
transform: translate(-25%, -50%) scale(0, 0);
border: 2px solid #FFFFFF;
opacity: 0;
}
90% {
transform: translate(-25%, -50%) scale(1, 1);
}
95% {
transform: translate(-25%, -50%) scale(2, 2);
border: 2px solid #FFFFFF;
opacity: 0.5;
}
96% {
transform: translate(-25%, -50%) scale(2, 2);
border: 2px solid #FFFFFF;
opacity: 0.5;
}
89% {
transform: translate(-25%, -50%) scale(0, 0);
border: 2px solid #FFFFFF;
opacity: 0;
}
100% {
transform: translate(-25%, -50%) scale(2.5, 2.5);
border: 2px solid #FFFFFF;
opacity: 0.2;
}
}
/* 最左侧蜡烛火焰跳动 */
@keyframes dance-fire {
0% {
left: 40.8%;
width: 16px;
height: 20px;
background-color: #FFC107;
}
3% {
left: 41.2%;
width: 16px;
height: 20px;
background-color: #FF9800;
}
7% {
left: 40.8%;
width: 16px;
height: 20px;
background-color: #FFC107;
}
11% {
left: 41.2%;
width: 16px;
height: 20px;
background-color: #FF9800;
}
15% {
left: 40.8%;
width: 16px;
height: 20px;
background-color: #FFC107;
}
19% {
left: 41.2%;
width: 16px;
height: 20px;
background-color: #FF9800;
}
23% {
left: 40.8%;
width: 16px;
height: 20px;
background-color: #FFC107;
}
27% {
left: 41.2%;
width: 16px;
height: 20px;
background-color: #FF9800;
}
31% {
left: 40.8%;
width: 16px;
height: 20px;
background-color: #FFC107;
}
35% {
left: 41.2%;
width: 16px;
height: 20px;
background-color: #FF9800;
}
39% {
left: 40.8%;
width: 16px;
height: 20px;
background-color: #FFC107;
}
43% {
left: 41.2%;
width: 16px;
height: 20px;
background-color: #FF9800;
}
47% {
left: 40.8%;
width: 16px;
height: 20px;
background-color: #FFC107;
}
51% {
left: 41.2%;
width: 16px;
height: 20px;
background-color: #FF9800;
}
55% {
left: 40.8%;
width: 16px;
height: 20px;
background-color: #FFC107;
}
58% {
left: 41.2%;
width: 16px;
height: 20px;
background-color: #FF9800;
}
59% {
left: 40%;
width: 0px;
height: 0px;
}
89% {
left: 40%;
width: 0px;
height: 0px;
}
90% {
left: 40.8%;
width: 16px;
height: 20px;
background-color: #FFC107;
}
94% {
left: 41.2%;
width: 16px;
height: 20px;
background-color: #FF9800;
}
}
/* 最左侧蜡烛摩擦生火的眼睛变小 */
@keyframes changeto-lower {
0%{
padding: 0px;
display: flex;
border-radius: 5px;
background-color: #673C63;
border-width: 0 0 0 0;
border: 0px solid #673C63;
transform: translate(-90%, 0%);
}
70% {
padding: 0px;
display: flex;
border-radius: 5px;
background-color: #673C63;
border-width: 0 0 0 0;
border: 0px solid #673C63;
transform: translate(-90%, 0%);
}
89% {
background-color: rgba(0, 0,0,0);
border: solid #673C63;
border-radius: 0px;
border-width: 0 2px 2px 0;
display: flex;
padding: 1px;
float: left;
transform-origin: 100% 0%;
transform: rotate(-45deg) translate(-50%, -65%);
}
90% {
padding: 0px;
display: flex;
border-radius: 5px;
background-color: #673C63;
border-width: 0 0 0 0;
border: 0px solid #673C63;
transform: translate(-90%, 0%);
}
71% {
background-color: rgba(0, 0,0,0);
border: solid #673C63;
border-radius: 0px;
border-width: 0 2px 2px 0;
display: flex;
padding: 1px;
float: left;
transform-origin: 100% 0%;
transform: rotate(-45deg) translate(-50%, -65%);
}
}
/* 最左侧蜡烛摩擦生火的眼睛变大 */
@keyframes changeto-greater {
0% {
top: 50%;
padding: 0px;
display: flex;
border-radius: 10px;
background-color: #673C63;
border-width: 0 0 0 0;
border: 0px solid #673C63;
transform: translate(-80%, 0%);
}
70% {
top: 50%;
padding: 0px;
display: flex;
border-radius: 10px;
background-color: #673C63;
border-width: 0 0 0 0;
border: 0px solid #673C63;
transform: translate(-80%, 0%);
}
89% {
top: 30%;
background-color: rgba(0, 0,0,0);
border: solid #673C63;
border-radius: 0px;
border-width: 0 2px 2px 0;
display: flex;
padding: 1px;
float: left;
transform-origin: 100% 0%;
transform: rotate(135deg) translate(-80%, 20%);
}
90% {
top: 50%;
padding: 0px;
display: flex;
border-radius: 10px;
background-color: #673C63;
border-width: 0 0 0 0;
border: 0px solid #673C63;
transform: translate(-80%, 0%);
}
71% {
top: 30%;
background-color: rgba(0, 0,0,0);
border: solid #673C63;
border-radius: 0px;
border-width: 0 2px 2px 0;
display: flex;
padding: 1px;
float: left;
transform-origin: 100% 0%;
transform: rotate(135deg) translate(-80%, 20%);
}
}
总结
基于js扩展的类web范式开发的css样式和标准的css有所不同,个人最直观感受有以下几点:
1)它不支持border属性里使用rgba()函数。
2)不支持background合并属性书写。
3)hml的元素display默认值为flex,容器内元素默认横向排列。
4)position的absolute定位相对的是父组件,z-index决定的是元素的渲染顺序,未设置position也能够使用。
此外,动画的火焰有点瑕疵,由于js扩展的类web范式开发的css样式不支持border-radius使用/分隔多组属性,蜡烛火焰没能实现宝塔形,只实现了坚果形。
如有错漏,请大家指正,每天进步一小点。
更多原创内容请关注:中软国际 HarmonyOS 技术团队
入门到精通、技巧到案例,系统化分享HarmonyOS开发技术,欢迎投稿和订阅,让我们一起携手前行共建鸿蒙生态。
想知道不选ets的主要原因有哪些?
两个蜡烛的动画做的真是可爱
js大家还是熟悉些吧
这个动画是用啥工具生成的吗,还是 一步步设置的
挺多类不支持还是蛮影响开发的
应该是一步步设置的
OpenHarmony的css样式未来会向标准的css样式靠拢吗?
未来可能会有OpenHarmony自己的标准吧