CSS画儿系列 | 大红灯笼高高挂,下面拜年的是福娃

1,830 阅读9分钟

我正在参加「兔了个兔」创意投稿大赛,详情请看:「兔了个兔」创意投稿大赛

前言

下周就要放假(请假)回家啦,趁着最后一个工作周,来充实下一X一度的CSS画儿系列。虽说是要祝大家「兔来运转」,但是这次就不画小兔子啦,画一对儿拜年的福娃「好运兔you」。

画起来

既然是一幅画,我们是不是就应该考虑构图啊、色彩啊、光影啊等等。

我用大毅力翻阅无数资料(并没有),观摩一堆名画(不可能),我决定这幅CSS画里该有的要素啥都没有。

简单形容一下,只有这些内容:喜庆的大红墙和木质地板作为场景的主体,顶部高高挂起的大红灯笼飘扬传递着新春祝福,窗外的夜色中星光点点,烟花不时绽放。最重要的是,在这热闹的氛围中,一对儿福娃给大家在给大家拜年。

不重要的主体场景

没有什么绿瓦红墙,但是光秃秃的也不好看,先来画一个室内角度的大红墙和简单的木质地板。

为了稍微做一点适配,这里采用了vwrem的方式。

<div class="container">
  <div class="wall"></div>
  <div class="floor"></div>
</div>
html{
  font-size:calc(100vw/750*100);
}

html,
body {
  margin: 0;
  padding: 0;
}
.container {
  margin: 0 auto;
  padding: 0;
  width: 7.5rem;
  height: 100vh;
  background-color: #17024c;
  position: relative;
}
.wall {
  width: 100%;
  height: 80vh;
  background-color: #9d2727;
  overflow: hidden;
}
.floor {
  width: 100%;
  height: 20vh;
  background-color: #67422a;
  background: linear-gradient(to bottom, #67422a, #371a0e);
}

接下来,在墙上加一个窗户,让我们可以在暖暖的室内看到外面的点点星光和烟花。

需要注意的是,我们看到的星光和烟花都是透过窗户看到的,所以窗户的overflow: hidden;样式是必不可少的。

<div class="window"></div>
.window {
  margin: 20vh auto 0;
  width: 80vh;
  height: 80vh;
  border-radius: 100%;
  border: 0.1rem solid #562c03;
  background-color: #17024c;
  overflow: hidden;
  position: relative;
}

rabbit01.png

很简单,我们不重要的主体场景就完成啦。

星光点缀下绽放的烟花

这里的星光偷懒了,因为使用码上掘金Vue3版本有莫名的报错,就不再循环生成多个随机位置的星星啦,大家如果自己要玩,这里可以搞出漫天星光。

在窗户内插入一些星星和烟花,这里的烟花参考了网上一个不错的效果,大家感兴趣的还可以继续细化一下:

<div class="window">
  <div class="starlight"></div>
  <div class="starlight starlight1"></div>
  <div class="starlight starlight2"></div>
  <div class="c-firework"></div>
  <div class="c-firework"></div>
  <div class="c-firework"></div>
  <div class="c-firework"></div>
  <div class="c-firework"></div>
</div>
.starlight {
  width: 0.04rem;
  height: 0.04rem;
  border-radius: 100%;
  background-color: #fff;
  position: absolute;
  left: 3rem;
  top: 3rem;
  &1 {
    opacity: 0.8;
    transform: scale(1.8);
    left: 3.2rem;
    top: 3.2rem;
  }
  &2 {
    opacity: 0.6;
    transform: scale(0.8);
    left: 3rem;
    top: 3.4rem;
  }
}
@keyframes fireworks-animation {
  0% {
    transform: translate(-50%, 90vh);
    width: 0.04rem;
    opacity: 1;
  }
  50% {
    width: 0.04rem;
    opacity: 1;
  }
  100% {
    width: 6rem;
    opacity: 0;
  }
}

.c-firework,
.c-firework::before,
.c-firework::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  aspect-ratio: 1;
  background: radial-gradient(circle, yellow 0.1rem, #000 0) 50% 00%,
    radial-gradient(circle, khaki 0.1rem, #000 0) 00% 50%,
    radial-gradient(circle, white 0.1rem, #000 0) 50% 99%,
    radial-gradient(circle, lime 0.1rem, #000 0) 99% 50%,
    radial-gradient(circle, crimson 0.1rem, #000 0) 80% 90%,
    radial-gradient(circle, red 0.1rem, #000 0) 95% 90%,
    radial-gradient(circle, yellow 0.1rem, #000 0) 10% 60%,
    radial-gradient(circle, khaki 0.1rem, #000 0) 31% 80%,
    radial-gradient(circle, white 0.1rem, #000 0) 80% 10%,
    radial-gradient(circle, lime 0.1rem, #000 0) 90% 23%,
    radial-gradient(circle, crimson 0.1rem, #000 0) 45% 20%,
    radial-gradient(circle, red 0.1rem, #000 0) 13% 24%;
  background-size: 0.08rem 0.08rem;
  background-repeat: no-repeat;
  transform: translate(-50%, -50%);
  animation: fireworks-animation 4s infinite;
}
.c-firework::before {
  transform: translate(-50%, -50%) rotate(25deg) !important;
}
.c-firework::after {
  transform: translate(-50%, -50%) rotate(-37deg) !important;
}

.c-firework:nth-of-type(2),
.c-firework:nth-of-type(2)::before,
.c-firework:nth-of-type(2)::after {
  top: 30%;
  left: 16%;
  animation-duration: 3.8s;
}
.c-firework:nth-of-type(3),
.c-firework:nth-of-type(3)::before,
.c-firework:nth-of-type(3)::after {
  top: 10%;
  left: 72%;
  animation-duration: 4.2s;
}
.c-firework:nth-of-type(4),
.c-firework:nth-of-type(4)::before,
.c-firework:nth-of-type(4)::after {
  top: 28%;
  left: 32%;
  animation-duration: 3.6s;
}
.c-firework:nth-of-type(5),
.c-firework:nth-of-type(5)::before,
.c-firework:nth-of-type(5)::after {
  top: 32%;
  left: 84%;
  animation-duration: 4.4s;
}
.c-firework:nth-of-type(6),
.c-firework:nth-of-type(6)::before,
.c-firework:nth-of-type(6)::after {
  top: 38%;
  left: 40%;
  animation-duration: 4.1s;
}
.c-firework:nth-of-type(7),
.c-firework:nth-of-type(7)::before,
.c-firework:nth-of-type(7)::after {
  top: 28%;
  left: 64%;
  animation-duration: 3.9s;
}
.c-firework:nth-of-type(8),
.c-firework:nth-of-type(8)::before,
.c-firework:nth-of-type(8)::after {
  top: 30%;
  left: 56%;
  animation-duration: 3.9s;
}

rabbit02.gif

挂起祝福的灯笼

大红灯笼是喜庆的代表,虽然现在春节已经很少能见到这物件,但是我们的CSS画里还是绝对不能少了这一抹传统色。

循环出4个灯笼,分别挂在两侧,每个灯笼上展示一个字,连在一句就是一句新春祝福,当然这里配合JavaScript食用效果更佳。

<div class="lantern-box">
  <div class="lantern">
    <div class="lantern-line"></div>
    <div class="lantern-body">
      <div class="lantern-part">
        <div class="lantern-text"></div>
      </div>
    </div>
    <div class="lantern-panicle">
      <div class="lantern-crown"></div>
      <div class="lantern-tassel"></div>
    </div>
  </div>
</div>
<div class="lantern-box lantern-box1">
<!--...-->
</div>
<div class="lantern-box lantern-box2">
<!--...-->
</div>
<div class="lantern-box lantern-box3">
<!--...-->
</div>
.lantern {
  position: relative;
  width: 1.2rem;
  height: 0.9rem;
  margin: 0.5rem;
  background: #d8000f;
  background: rgba(216, 0, 15, 0.8);
  border-radius: 50% 50%;
  transform-origin: 50% -1rem;
  animation: swing 5s infinite ease-in-out;
  box-shadow: -0.05rem 0.05rem 0.3rem 0.04rem rgba(252, 144, 61, 1);
  &::before {
    position: absolute;
    top: -0.07rem;
    left: 0.29rem;
    height: 0.12rem;
    width: 0.6rem;
    content: " ";
    display: block;
    z-index: 1999;
    border-radius: 0.05rem 0.05rem 0 0;
    border: solid 0.01rem #dc8f03;
    background: #ffa500;
    background: linear-gradient(
      to right,
      #dc8f03,
      #ffa500,
      #dc8f03,
      #ffa500,
      #dc8f03
    );
  }
  &::after {
    position: absolute;
    bottom: -0.07rem;
    left: 0.1rem;
    height: 0.12rem;
    width: 0.6rem;
    content: " ";
    display: block;
    margin-left: 0.2rem;
    border-radius: 0 0 0.05rem 0.05rem;
    border: solid 0.01rem #dc8f03;
    background: #ffa500;
    background: linear-gradient(
      to right,
      #dc8f03,
      #ffa500,
      #dc8f03,
      #ffa500,
      #dc8f03
    );
  }
  &-box {
    position: absolute;
    top: -0.2rem;
    left: -0.2rem;
    z-index: 1999;
    &1 {
      left: 0.6rem;
    }
    &2 {
      left: auto;
      right: 0.6rem;
    }
    &3 {
      left: auto;
      right: -0.2rem;
    }
  }
  &-box1 &,
  &-box3 & {
    animation: swing 3s infinite ease-in-out;
  }
  &-line {
    position: absolute;
    top: -0.6rem;
    left: 0.6rem;
    width: 0.06rem;
    height: 0.6rem;
    background: #dc8f03;
  }

  &-body {
    width: 1rem;
    height: 0.9rem;
    background: #d8000f;
    background: rgba(216, 0, 15, 0.1);
    margin: 0.12rem 0.08rem 0.08rem 0.1rem;
    border-radius: 50% 50%;
    border: 0.02rem solid #dc8f03;
  }
  &-part {
    width: 0.45rem;
    height: 0.9rem;
    background: #d8000f;
    background: rgba(216, 0, 15, 0.1);
    margin: -0.04rem 0.08rem 0.08rem 0.26rem;
    border-radius: 50% 50%;
    border: 0.02rem solid #dc8f03;
  }

  &-text {
    font-family: "华文行楷", Arial, Lucida Grande, Tahoma, sans-serif;
    font-size: 0.6rem;
    color: #dc8f03;
    font-weight: bold;
    line-height: 0.85rem;
    text-align: center;
  }

  &-panicle {
    position: relative;
    width: 0.05rem;
    height: 0.2rem;
    margin: -0.05rem 0 0 0.59rem;
    animation: swing 4s infinite ease-in-out;
    -webkit-animation: swing 4s infinite ease-in-out;
    transform-origin: 50% -0.45rem;
    -webkit-transform-origin: 50% -0.45rem;
    background: #ffa500;
    border-radius: 0 0 0.05rem 0.05rem;
  }
  &-crown {
    position: absolute;
    top: 0.18rem;
    left: -0.02rem;
    width: 0.1rem;
    height: 0.35rem;
    background: #ffa500;
    border-radius: 0 0 0 0.05rem;
  }
  &-tassel {
    position: absolute;
    top: 0.14rem;
    left: -0.02rem;
    width: 0.1rem;
    height: 0.1rem;
    background: #dc8f03;
    border-radius: 50%;
  }
}

@keyframes swing {
  0% {
    transform: rotate(-10deg);
  }
  50% {
    transform: rotate(10deg);
  }
  100% {
    transform: rotate(-10deg);
  }
}

@keyframes swing2 {
  0% {
    transform: rotate(10deg);
  }
  50% {
    transform: rotate(-10deg);
  }
  100% {
    transform: rotate(10deg);
  }
}

rabbit03.gif

此处可以使用animation结合transform让灯笼简单的动起来,别忘了先使用transform-origin: 50% -1rem;把旋转的原点设置到灯笼顶部居中上方的位置。

拜年的福娃娃

毕竟两个福娃有很多相似性,为了节省代码,我们有大量可以复用的元素和样式。除了小男孩有一个瓜皮帽,小女孩展现了漂亮的发髻,这些我们再区分处理即可。

<div class="fuwa boy">
  <div class="head">
    <div class="hat">
      <div class="hat-bead"></div>
      <div class="hat-cloth">
        <div class="hat-skeleton"></div>
        <div class="hat-skeleton hat-skeleton2"></div>
        <div class="hat-skeleton hat-skeleton3"></div>
      </div>
      <div class="hat-brim"></div>
    </div>
    <div class="face">
      <div class="signet"></div>
      <div class="ear"></div>
      <div class="ear ear2"></div>
      <div class="eye"></div>
      <div class="eye eye2"></div>
      <div class="blush"></div>
      <div class="blush blush2"></div>
      <div class="mouth"></div>
    </div>
  </div>
  <div class="neck"></div>
  <div class="body">
    <div class="arm">
      <div class="cuff"></div>
      <div class="hand"></div>
    </div>
    <div class="arm arm-right">
      <div class="cuff"></div>
      <div class="hand"></div>
    </div>
  </div>

</div>
.fuwa {
  padding: 0.3rem 0;
  position: absolute;
  bottom: 0;
  left: 1rem;
  .head {
    transform-origin: bottom center;
    animation: head-ani 1s infinite ease-in-out;
    position: relative;
    z-index: 2;
  }
  .hat {
    margin: 0 auto;
    width: 1.9rem;
    position: relative;
    z-index: 3;
    &-bead {
      margin: 0 auto;
      width: 0.3rem;
      height: 0.3rem;
      border-radius: 100%;
      background-color: #f7d185;
    }

    &-cloth {
      margin: -0.12rem auto 0;
      width: 1.8rem;
      height: 0.8rem;
      border-radius: 100% 100% 0 0;
      background-color: #e3675c;
      overflow: hidden;
      position: relative;
    }
    &-skeleton {
      width: 0.04rem;
      height: 1.1rem;
      border-radius: 50% 50% 0 0 / 100% 100% 0 0;
      // background-color: #C95E54;
      border: 0.05rem solid #c95e54;
      border-radius: 100%;
      border-right: none;
      position: absolute;
      top: 0;
      left: 0.88rem;
      transform-origin: 0 0;
      transform: rotate(45deg);
      &2 {
        transform: rotate(0deg);
      }
      &3 {
        transform: rotate(-45deg);
      }
    }

    &-brim {
      margin: -0.1rem auto 0;
      width: 1.9rem;
      height: 0.2rem;
      border-radius: 0.2rem;
      background-color: #f7d185;
      position: relative;
    }
  }
  
  // face
  .face {
    margin: -0.6rem auto 0;
    width: 2rem;
    height: 2rem;
    border-radius: 100%;
    background-color: #f7e1b6;
    position: relative;
    z-index: 2;
  }
  .signet {
    margin: 0 auto;
    width: 0.1rem;
    height: 0.1rem;
    border-radius: 100%;
    background-color: #e3655b;
    position: absolute;
    top: 0.8rem;
    left: 50%;
    transform: translate(-0.05rem, 0);
  }
  .eye {
    width: 0.5rem;
    height: 0.12rem;
    position: absolute;
    top: 1rem;
    left: 0.4rem;
    overflow: hidden;
    animation: eye-ani 1s infinite ease-in-out;
    &::before {
      content: "";
      display: block;
      width: 0.4rem;
      height: 0.2rem;
      border: 0.05rem solid #996250;
      border-radius: 100%;
      // border-radius: 50% 50% 0 0/100% 100% 0 0;
      border-bottom: none;
    }
    &2 {
      left: auto;
      right: 0.4rem;
    }
  }
  .ear {
    width: 0.6rem;
    height: 0.3rem;
    border-radius: 0.6rem 0.6rem 0 0;
    transform: rotate(273deg);
    background-color: #f8d899;
    position: absolute;
    top: 0.8rem;
    left: -0.36rem;
    &:before {
      content: "";
      width: 0.6rem;
      height: 0.2rem;
      border-radius: 0.4rem 0.4rem 0 0;
      background-color: #f7e1b6;
      position: absolute;
      bottom: -0.1rem;
      left: 0;
    }
    &2 {
      transform: rotate(87deg);
      left: auto;
      right: -0.36rem;
    }
  }
  .blush {
    width: 0.3rem;
    height: 0.16rem;
    border-radius: 100%;
    background-color: #f2c0bc;
    position: absolute;
    top: 1.3rem;
    left: 0.2rem;
    &2 {
      left: auto;
      right: 0.2rem;
    }
  }
  .mouth {
    width: 0.4rem;
    height: 0.2rem;
    border-radius: 0 0 0.4rem 0.4rem;
    background-color: #e3655b;
    position: absolute;
    top: 1.6rem;
    left: 50%;
    transform: translate(-0.2rem);
    animation: mouth-ani 1s infinite ease-in-out;
  }
  .neck {
    margin: -0.1rem auto 0;
    width: 0.4rem;
    height: 0.3rem;
    border-radius: 0 0 0.4rem 0.4rem;
    background-color: #e3655b;
    position: relative;
    z-index: 1;
  }
  .body {
    width: 2.4rem;
    height: 0.6rem;
    margin-top: -0.16rem;
    position: relative;
    z-index: 4;
    animation: body-ani 0.6s infinite ease-in-out;
  }
  .arm {
    width: 1rem;
    height: 0.44rem;
    border-radius: 1rem 0 0 0.44rem/ 0.44rem 0 0 0.4rem;
    background-color: #e3655b;
    position: absolute;
    left: 0.1rem;
    &-right {
      left: auto;
      right: 0.1rem;
      transform: rotate(-180deg);
      z-index: 5;
    }
  }
  .cuff {
    width: 0.2rem;
    height: 0.5rem;
    border-radius: 0.4rem 0.4rem 0.02rem 0.02rem / 0.4rem 0.2rem 0 0;
    background-color: #f7d185;
    position: absolute;
    top: -0.04rem;
    right: 0;
  }
  .hand {
    width: 0.4rem;
    height: 0.2rem;
    border-radius: 0.4rem 0.4rem 0 0;
    background-color: #f8d899;
    border: 0.02rem solid #f7d185;
    position: absolute;
    right: -0.3rem;
    top: 0.1rem;
    transform: rotate(90deg);
  }
}

@keyframes head-ani {
  0% {
    transform: rotate(-6deg);
  }

  50% {
    transform: rotate(6deg);
  }

  100% {
    transform: rotate(-6deg);
  }
}

@keyframes eye-ani {
  0% {
    transform: scaleY(1);
  }

  50% {
    transform: scaleY(1.5);
  }

  100% {
    transform: scaleY(1);
  }
}
@keyframes mouth-ani {
  0% {
    transform: translate(-0.2rem) scaleY(1);
  }

  50% {
    transform: translate(-0.2rem) scaleY(1.5);
  }

  100% {
    transform: translate(-0.2rem) scaleY(1);
  }
}
@keyframes body-ani {
  0% {
    transform: translateY(0);
  }
  25% {
    transform: translateY(-0.06rem);
  }
  50% {
    transform: translateY(0);
  }
  75% {
    transform: translateY(-0.02rem);
  }
  100% {
    transform: translateY(0);
  }
}

rabbit04.gif

小女孩有漂亮的发髻,需要我们单独处理一下子。

<div class="fuwa girl">
  <div class="head">
    <div class="hair">
      <div class="hair-bun">
        <!-- <div class="hair-ribbon"></div> -->
        <div class="hair-rope"></div>
      </div>
      <div class="hair-bun hair-bun-right">
        <!-- <div class="hair-ribbon"></div> -->
        <div class="hair-rope"></div>
      </div>

      <div class="hair-forehead"></div>
    </div>
    <!-- face... -->
  </div>
  <!-- neck... -->
</div>
.fuwa {
  // hair
  .hair {
    margin: 0 auto;
    width: 1.9rem;
    position: relative;
    z-index: 3;
    &-bun {
      width: 0.6rem;
      height: 0.6rem;
      border-radius: 0.4rem;
      background-color: #996250;
      transform: rotate(-45deg);
      // overflow: hidden;
      position: absolute;
      left: 0;
      top: -0.2rem;
      &-right {
        left: auto;
        right: 0;
        transform: rotate(45deg);
        .hair-ribbon {
          &::before,
          &::after {
            left: auto;
            right: -0.8rem;
            border-radius: 0.8rem / 0 0.8rem 0.2rem 0;
            transform-origin: center left;
            transform: rotate(-20deg);
            animation: ribbon-ani3 1s infinite ease-in-out;
          }
          &:after {
            border-radius: 0.8rem / 0 0.2rem 0.8rem 0;
            transform: rotate(0);
            animation: ribbon-ani4 1s infinite ease-in-out;
          }
        }
      }
    }
    &-rope {
      width: 0.6rem;
      height: 0.3rem;
      position: absolute;
      bottom: 0;
      &::before,
      &::after {
        content: "";
        display: block;
        width: 0.3rem;
        height: 0.3rem;
        border-radius: 100%;
        background-color: #f7d185;
        position: absolute;
        left: 0;
      }
      &:after {
        left: auto;
        right: 0;
      }
    }
    &-ribbon {
      width: 0.6rem;
      height: 0.3rem;
      position: absolute;
      bottom: 0;
      &::before,
      &::after {
        content: "";
        display: block;
        width: 1rem;
        height: 0.2rem;
        border-radius: 0.8rem 0.8rem 0 0/ 0.8rem 0.2rem;
        background-color: #e3655b;
        position: absolute;
        top: 0;
        left: -0.8rem;
        transform-origin: right center;
        transform: rotate(20deg);
        animation: ribbon-ani1 1s infinite ease-in-out;
      }
      &::after {
        width: 0.8rem;
        left: -0.8rem;
        border-radius: 0.8rem 0 0 0.8rem / 0.2rem 0 0 0.8rem;
        transform: rotate(0);
        animation: ribbon-ani2 1s infinite ease-in-out;
      }
    }
    &-forehead {
      margin: 0 auto;
      width: 2rem;
      height: 1rem;
      border-radius: 2rem 2rem 0 0;
      background-color: #996250;
      position: relative;
      &::before,
      &::after {
        content: "";
        width: 0;
        height: 0;
        border-width: 0 0.08rem 0.2rem;
        border-style: solid;
        border-color: transparent transparent #f7e1b6;
        position: absolute;
        bottom: 0;
        left: 0.4rem;
      }
      &::after {
        left: auto;
        right: 0.4rem;
      }
    }
  }
}

rabbit05.gif

Emm,由于这个流程主打CSS来实现,但是为了能简单适配一下不同屏幕的展示效果,来使用JavaScript简单处理一下。

window.onload = function() {
  const width = document.body.clientWidth;
  const height = document.body.clientHeight;
  if(height > width) {
    // document.querySelector('.wall').style.height = '80vw';
    document.querySelector('.window').style.width = '80vw';
    document.querySelector('.window').style.height = '80vw';
    document.querySelector('.window').style.marginTop = 'calc(80vh - 80vw)';
  }
}

写在最后

说着是一幅画,其实只能算是个毛坯房,如果你也感兴趣,可以往不同的方向玩起来,比如灯笼上面的祝福语可以动态输入生成,比如可以做成图片或者GIF自定义分享传递祝福,再比如你也可以把福娃修饰成自己的样子。

不管怎么说,希望这幅《福娃拜年》能“兔”你一乐,兔年顶呱呱。