HarmonyOS - 纯CSS实现吹灭蜡烛动画 原创 精华

中软HOS小鸿
发布于 2022-8-29 10:13
浏览
4收藏

作者:炒栗子

前言

前段时间HarmonyOS 3发布了,吸引了不少的眼球,为了体验鸿蒙应用开发,决定动手实现一个案例——通过css动画实现吹灭蜡烛动画,看了一下鸿蒙应用开发文档,有js和ets两种开发方式,综合考量了一下,决定采用js方式实现。

效果展示

HarmonyOS - 纯CSS实现吹灭蜡烛动画-鸿蒙开发者社区

实现思路

  1. 通过变换translate()的X轴位置,实现最右边蜡烛眼睛移动效果;

  2. 通过改变height、width和left属性实现最右边蜡烛的嘴巴吹起动画;

  3. 通过scale()和translate()动画函数实现最右侧蜡烛吹起过程的身体变化;

  4. 通过改变left属性实现最左侧蜡烛的水平方向的烟雾;

  5. 通过改变left和top属性实现最左侧蜡烛的竖直方向的烟雾;

  6. 通过变换translate()的Y轴方向、scale()、background-color、box-shadow和border-color实现蜡烛被吹灭时左移效果和蜡烛摩擦生火体色的变化;

  7. 通过translate()和rotateZ()函数实现蜡烛被吹灭时的左右抖动以及烛芯摩擦生火动画;

  8. 通过border透明度、translate()和scale()实现烛光点燃时产生的光晕;

  9. 通过backgroun-color、left、width和height实现火焰闪烁效果,通过translate-origin、rotate()和translate()实现眨眼效果;

  10. 通过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开发技术,欢迎投稿和订阅,让我们一起携手前行共建鸿蒙生态。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
8
收藏 4
回复
举报
8条回复
按时间正序
/
按时间倒序
红叶亦知秋
红叶亦知秋

想知道不选ets的主要原因有哪些?

回复
2022-8-29 10:20:46
FlashinMiami
FlashinMiami

两个蜡烛的动画做的真是可爱

回复
2022-8-31 11:34:31
带带小老弟
带带小老弟 回复了 红叶亦知秋
想知道不选ets的主要原因有哪些?

js大家还是熟悉些吧

已于2022-9-1 10:24:34修改
回复
2022-9-1 10:23:46
笨笨的婧婧
笨笨的婧婧

这个动画是用啥工具生成的吗,还是 一步步设置的

回复
2022-9-1 11:54:12
有故事的王同学
有故事的王同学

挺多类不支持还是蛮影响开发的

回复
2022-9-1 17:20:11
香菜太难吃了
香菜太难吃了 回复了 笨笨的婧婧
这个动画是用啥工具生成的吗,还是 一步步设置的

应该是一步步设置的

回复
2022-9-2 14:33:10
0aaron
0aaron

OpenHarmony的css样式未来会向标准的css样式靠拢吗?

回复
2022-9-2 16:28:11
真庐山升龙霸
真庐山升龙霸 回复了 0aaron
OpenHarmony的css样式未来会向标准的css样式靠拢吗?

未来可能会有OpenHarmony自己的标准吧

回复
2022-9-2 17:50:10
回复
    相关推荐