likes
comments
collection
share

✨✨使用 Sass 函数制作一个落雪背景

作者站长头像
站长
· 阅读数 19

开篇

周五继续在 codepan 寻找灵感的时候发现了一个使用 Sass 来完成的一个落雪效果,大致看了一下,是通过 Sass 的 random 函数配合其他函数来实现的,通过 random 定义多个不确定变量来实现各个元素之间的大小位置和动画效果

因为平时虽然也有用到过 Sass、less 之类的 CSS预编译语言,但是最常用的其实也就是 嵌套、变量、混入 这些语法,对函数的使用率很低,所以也趁机学习一下吧~

创建 DOM 结构

既然是 落雪 的效果,那么肯定不可能只有一两片雪,是需要创建很多个 dom 元素的。

如果在 html 部分手动复制粘贴的话,不仅会多很多代码量,看上去也很难受。那么如何快速的创建多个类似的 dom 元素呢?

除了 CSS 和 HTML,我们还有 JavaScript。

首先,先把外层的父元素确定下来

<div class="scss-snow-bg"></div>

然后,将它铺满页面,并添加一个背景色

html,
body {
  width: 100vw;
  height: 100vh;
}

.scss-snow-bg {
  width: 100%;
  height: 100%;
  background: radial-gradient(ellipse at bottom, #1b2735 0%, #090a0f 100%);
  overflow: hidden;
  filter: drop-shadow(0 0 10px white);
}

此时,就得到了一个 夜晚场景

✨✨使用 Sass 函数制作一个落雪背景

下面,就是通过 JS 来创建 很多 子元素了。

JS 创建雪花

上面说了,雪花可能有很多片,这里我们假设有 两百片雪花,如果手动写 html 的话,就需要粘贴很多,那么更方便的肯定就是通过 JS 来创建了。

JS 创建元素的三种方式

目前创建元素的方式一般有三种:

  1. document.write():页面加载时执行会在页面末尾接着插入;而在页面加载完毕之后再执行则会清空页面所有内容再插入
  2. element.innerHTML:会清空元素内部的所有内容再重新插入编辑的内容
  3. document.createElement():只会在内存中创建一个元素对象,需要手动调用 element.appendChild 将该元素插入到目标元素中

如果我们只是需要在页面中插入一些新元素而不想影响页面其他内容,一般都是采用第三种方式

此时可以这么操作:

const box = document.querySelector('.scss-snow-bg');

for(let i = 0; i < 200; i++) {
    let snow = document.createElement('div')
    snow.classList.add('snow')
    box.appendChild(snow)
}

但是,这样会造成页面重复渲染多次,十分影响性能和用户体验。那么此时我们可以通过 DocumentFragment 文档片段来优化。

将循环创建的 snow 节点都添加到该片段中,最后统一一次性插入到父元素。

const box = document.querySelector('.scss-snow-bg');

const fragment = document.createDocumentFragment();

for(let i = 0; i < 200; i++) {
    let snow = document.createElement('div')
    snow.classList.add('snow')
    fragment.appendChild(snow)
}

box.appendChild(fragment)

此时,我们就得到了一个具有两百个 div.snow 的父节点div.scss-snow-bg

Sass 相关语法

首先,我们先来复习一下这个效果中用到的 Sass 的 random 随机函数 和其他特性。

random

该函数在 Sass 中被称为 数字函数,与 JS 中的 Math.random 函数作用类似,都是用来 创建一个随机数。没有参数时是直接创建 0 ~ 1 之间的随机数,并且一般会伴随着四五位小数;而传递参数 n 之后,则是 创建一个 1 ~ n 之间的随机数

而与 Math.random 不同的是,Sass 的 random 函数在传递参数时 该参数值必须大于 1 且是正整数,不然会报错,并且结果也 不是 0 ~ n,而是 1 ~ n

该函数生成的值可以通过 #{} 组合成一个字符串,或者通过与 有单位的值 进行计算得到一个 css 样式值。

floor

该函数就与 JS 中的 Math.floor 基本一致了,都是 向下取整数值

当然 Sass 中还有很多数字相关的处理函数,有兴趣的同学可以查看官方文档。

@function

这个语法在 Sass 中也被称为 指令,可以配合 @return 实现自定义函数。与 JS 中的 function 和 return 关键字 差不多。

但是,Sass 中定义的函数必须有一个 直接可用的 返回值,而不像 JS 那样可以默认返回一个 undefined

动画

因为雪花肯定是白色的,而且为了定位方便,一般都采用绝对定位的方式来处理,所以此时我们先给 雪花 加上一个基础样式。

.snow {
  $total: 200;
  position: absolute;
  width: 10px;
  height: 10px;
  background: white;
  border-radius: 50%;
}

这里先定义了一个变量 $total,表示我们的雪花总数有两百个

实现雪花的动画用到的肯定不止是从 1 开始的随机数就能满足的,需要实现一个 范围内随机数的 工具函数

所以先定义个范围内随机数的自定义函数 random_range

@function random_range($min, $max) {
  $rand: random();
  $random_range: $min + floor($rand * (($max - $min) + 1));
  @return $random_range;
}

其实这个函数的思路与 JS 生成范围内随机数的思路一致。

然后,就是 从 0 开始循环生成动画和定位部分

  @for $i from 1 through $total {
    $random-x: random(1000000) * 0.0001vw;
    $random-offset: random_range(-100000, 100000) * 0.0001vw;
    $random-x-end: $random-x + $random-offset;
    $random-x-end-yoyo: $random-x + ($random-offset / 2);
    $random-yoyo-time: random_range(30000, 80000) / 100000;
    $random-yoyo-y: $random-yoyo-time * 100vh;
    $random-scale: random(10000) * 0.0001;
    $fall-duration: random_range(10, 30) * 1s;
    $fall-delay: random(30) * -1s;

    &:nth-child(#{$i}) {
      opacity: random(10000) * 0.0001;
      transform: translate($random-x, -10px) scale($random-scale);
      animation: fall-#{$i} $fall-duration $fall-delay linear infinite;
    }

    @keyframes fall-#{$i} {
      #{percentage($random-yoyo-time)} {
        transform: translate($random-x-end, $random-yoyo-y) scale($random-scale);
      }

      to {
        transform: translate($random-x-end-yoyo, 100vh) scale($random-scale);
      }
    }
  }

这里为什么随机数需要取 这么大的范围,最后又乘一个很小的小数?

个人认为也是为了 减小随机数会生成的多位小数造成的显示误差

最后为每个雪花都定义了一个专属的动画 @keyframes fall-#{$i} ,也是让每片雪花的 动画、大小、透明度、动画运动路径都不一样了。

最后

虽然效果看起来比较简单,但是相关的一些语法和函数在平时也确实很少泳道,偶尔看看大佬们实现的一些动画效果,对自己的启发和技术积累也有很大帮助。

最后也希望自己的文章能给你带来一点小小的帮助~~

往期精彩

Bpmn.js 进阶指南

Vue 2 源码阅读理解

一行指令实现大屏元素分辨率适配(Vue)

基于 Vue 2 与 高德地图 2.0 的“线面编辑器”