前端就是要炫一点

1,202 阅读7分钟

概括

作为直接面向用户的守门员,一个合格的前端当然要会做一些比较炫酷有意思的页面效果,现在后端会写页面了,测试会做管理后台了,只有高级动效能体现(装)价值(B)了。

实现方式

一般也就这几种了

  • 纯css撸,使用transition animation
  • svg
  • gif
  • canvas
  • lottie

src=http___i1.hdslb.com_bfs_archive_8fadf6c430fbc67c1239e4e419b7cc78224f8460.jpg&refer=http___i1.hdslb.jpeg

一、纯css撸

这种方式讲究慢工出细活,只要耐心,一般的动效都可以做出来

transition

只要设置的属性发生了变化即执行过渡效果,比较easy

/**
 * property 过渡效果的 CSS 属性的名称
 * duration 过渡效果时间
 * timing-function 过度效果速度曲线 默认ease=慢快慢 linear=匀速 ease-in=慢开 ease-out=慢关 ease-in-out=慢开慢关 cubic-bezier(n,n,n,n)-自由配置0~1之间的数值
 * delay 过渡效果延迟时间
*/
transition: property duration timing-function delay;

animation+@keyframes

一对好基友,啥动画都有!

1627125301558.gif

@keyframes定义动画名称和具体执行效果,搭配transform可以有效实现元素的平移、旋转、缩放效果。

/**
 * name 关键帧的名称
 * duration 动画执行时间
 * timing-function 动画执行效果 默认ease=慢快慢 linear=匀速 ease-in=慢开 ease-out=慢关 ease-in-out=慢开慢关 cubic-bezier(n,n,n,n)-自由配置0~1之间的数值
 * delay 过渡效果延迟时间
 * iteration-count 动画播放次数 常用infinite-无限 或 数字
 * 其他属性用的较少,需要可细查
*/
animation: name duration timing-function delay iteration-count direction fill-mode play-state;

如果项目使用了sass, 可以使用变量$test、@mixin代码块、@for循环来完成类似于流星雨、存钱罐等多个元素的动画控制

// $i 表示变量
// @for $i from <start> through <end>
// @for $i from <start> to <end>

$Company:1px;  // 单位基数
$base-color: #1875e7;

@for $i from 1 through 3 {
  ul:nth-child(#{$i}) {
    transform:translateY(-60 * $Company) translateX( (random(160) - 30) * $Company);
    animation-delay: random(10) * .1s; 
    animation-duration: random(10) * .1s + .7s;
    background-color: lighten($base-color, $i * 5%);
  }
}

另外,推荐一个开源的css效果库animate.css animate.style/ 如果只用到了一个或几个效果大可不必npm或者地址全量引入,直接打开线上源码ctrl+V出来即可。

image.png

png序列帧或精灵图

所谓的序列帧,就是N多张图片的快速切换,类似小时候玩的小人书。序列图片视频化技术实现方法很多

  • 合在一张大图上,控制background-position或者transform实现播放效果。此方法优点是方便快捷,但是,background-position定位性能不怎么样,只适用于小元素小动画,例如一些loading效果,如果是全屏的大图切换,则在移动设备上可以明显感觉到卡顿;
  • 图片一次性在页面上,依次控制显隐。如果序列帧太多,还是存在性能问题,而且不好监控所有图片加载完成控制
  • 页面上一个<img>元素,然后不断改变src地址,每次切换都是先去拿图片资源,效果不是很好,容易卡顿。

考虑到页面性能,目前较为常见的处理方案是,预加载所有序列帧图片,预加载完成之后,执行序列帧的切换播放

直接上代码了

// vue
data() {
    return {
      indexRange: [0, 143],
      maxLength: 143,
      eleContainer: '',
      store: {
        length: 0
      }
    }
},
mounted() {
    this.eleContainer = document.getElementById('container');
    this.initFrame(); // 初始化动画
},
methods: {
    // 预加载图片
    initFrame() {
      const _this = this;
      const urlRoot = "https://test.img.com/a/b/c";
      // 图片序列预加载
      for (let start = this.indexRange[0]; start <= this.indexRange[1]; start++) {
        (function(index) {
          var img = new Image();
          img.onload = function() {
            _this.store.length++;
            // 存储预加载的图片对象
            _this.store[index] = this;
            _this.play();
          };
          img.onerror = function() {
            _this.store.length++;
            _this.play();
          };
          img.src = urlRoot + index + '.jpg';
        })(start);
      }
    },
    // 播放控制
    play() {
      const _this = this;
      let percent = Math.round(100 * (this.store.length / this.maxLength));
      // 全部加载完毕,无论成功还是失败
      if (percent === 100) {
        let index = this.indexRange[0];
        let removeFront = true;
        this.eleContainer.innerHTML = '';
        // 依次append图片对象
        var step = function() {
          if (_this.store[index - 1]) {
            try {
              _this.eleContainer.removeChild(_this.store[index - 1]);
            } catch (error) {
              console.log(error);
            }
          }
          _this.eleContainer.appendChild(_this.store[index]);
          // 序列增加
          index++;
          // 如果超过最大限制
          if (index <= _this.indexRange[1]) {
            setTimeout(step, 62);
          } else {
            // 播放结束执行逻辑
          }
        };
        // 等100%动画结束后执行播放
        setTimeout(step);
      }
    }
}

写在最后:序列帧做动画的优点是播放可控,局部或者全部循环播放都可以在播放结束逻辑里执行。缺点是序列帧做动画适合做图片数量较少,或者说图片体积比较小的动画效果。实测整个序列帧图片包的体积控制在2M以内,这样预加载所有图片所需的时间可以控制在2s以内,不影响正常的页面加载感官效果。如果太大了,预加载时间太长了,得不偿失了,不仅体验差,用户的流量就是这么没的

svg

SVG是一种基于XML的,用于定义缩放矢量图形的标记语言。通过定义必要的线和形状来创建一个图形,也可以修改已有的位图,或者将这两种方式结合起来创建图形。

主要的绘制方式

<svg width="200" height="250" version="1.1" xmlns="http://www.w3.org/2000/svg">
    // 矩形绘制
    <rect x="10" y="10" width="30" height="30" stroke="black" fill="transparent" stroke-width="5"/>
    // 圆形和椭圆
    <circle cx="25" cy="75" r="20" stroke="red" fill="transparent" stroke-width="5"/>
    <ellipse cx="75" cy="75" rx="20" ry="5" stroke="red" fill="transparent" stroke-width="5"/>
    // 直线和曲线
    <line x1="10" x2="50" y1="110" y2="150" stroke="orange" fill="transparent" stroke-width="5"/>
    <polyline points="60 110 65 120 70 115 75 130 80 125 85 140 90 135 95 150 100 145"
      stroke="orange" fill="transparent" stroke-width="5"/>
    // 多边形
    <polygon points="50 160 55 180 70 180 60 190 65 205 50 195 35 205 40 190 30 180 45 180"
      stroke="green" fill="transparent" stroke-width="5"/>
    // 路径
    <path d="M20,230 Q40,205 50,230 T90,230" fill="none" stroke="blue" stroke-width="5"/>
</svg>

如果要用纯svg做动效就要结合animation来实现动画效果,如果是复杂的图形效果,主要是用到path(路径)来绘制。类似骨架屏这种的,就可以用svg根据不同页面的布局结构很方便的做出来。

// html
<div id="home_skeleton" class="main-bone" style="display: none;"></div>
// css
.main-bone {
  position: fixed;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  z-index: 1000;
  background: #f4f5f7;
  transition: all .6s linear;
  -webkit-transition: all .6s linear;
  pointer-events: none;
}
// js
const skeleton = document.getElementById('home_skeleton');
skeleton.innerHTML = skeletonHtml; // skeletonHtml定义的svg结构
skeleton.style.opacity = '1';
skeleton.style = 'display:block';

svga

src=http___hbimg.huabanimg.com_2b8fb4c6d68c1b7442bd825f373c22358fd3f394109a4-DD3kG4_fw658&refer=http___hbimg.huabanimg.gif

敲黑板,重点来了,SVGA 是一种同时兼容 iOS / Android / Flutter / Web 多个平台的动画格式。复杂的动画效果我们来画有点费劲而且还原度也很难让设计师满意。这个时候借助svga,让设计通过AE+svga插件,导出一个动画的svga格式的一个二进制文件,我们直接调用svga的API就可以直接展示动画,岂不简单粗暴,皆大欢喜。

安装

npm install svgaplayerweb --save

使用

// 方式一
// 定义展示块
<div id="demoCanvas" style="styles..."></div>
// 播放逻辑
var player = new SVGA.Player('#demoCanvas');
var parser = new SVGA.Parser('#demoCanvas'); // Must Provide same selector eg:#demoCanvas IF support IE6+
parser.load('rose_2.0.0.svga', function(videoItem) {
    player.setVideoItem(videoItem);
    player.startAnimation();
})
// 方式二
<div src="rose_2.0.0.svga" loops="0" clearsAfterStop="true" style="styles..."></div>

此外只要知道了图像的键名,还可以自己替换特定的图像,或者在特定的图像上添加文本

player.setImage('http://yourserver.com/xxx.png', 'ImageKey'); // 替换图片
player.setText({ 
    text: 'Hello, World!', 
    family: 'Arial',
    size: "24px", 
    color: "#ffe0a4",
    offset: {x: 0.0, y: 0.0}
}, 'ImageKey'); // 添加图像文本

优缺点

优点/优势

  • 方便简单,解放双手,节省大量脑细胞与时间。视觉提升显著,能够和设计师的关系更加融洽。
  • 性能提升,比gif强一万倍
  • 支持音频播放
  • 跨平台,多端皆可svga 缺点/问题
  • Android 4.x 兼容,操作系统缺乏 Blob 的支持,所以需要手动添加 Blob 到代码中
<script src="//cdn.bootcss.com/blob-polyfill/1.0.20150320/Blob.min.js"></script>

参考链接:github.com/svga/SVGAPl…

gif

这个不必多说了吧,额,虽然很方便,但是gif是位图,不高清,加载慢,消耗性能,酌情使用。

canvas

不得不说canvas真的是非常好的一件装备,无论是图表、刻度划尺、波浪图等等,在做一些比较简单而且需要逻辑交互的动效或者功能的时候,canvas是最合适的,自己做的东西我们想怎么玩就怎么玩。

Lottie

Lottie一个适用于Web,Android,iOS,React Native和Windows 的移动库,它可以使用Bodymovin解析以json 格式导出的Adobe After Effects动画,并在移动设备上进行本地渲染!

src=http___hbimg.huabanimg.com_3fee54d0b2e0b7a132319a8e104f5fdc2edd3d35d03ee-93Jmdq_fw658&refer=http___hbimg.huabanimg.gif

web端我们主要用到的lottie-web, 默认svg的渲染方式,核心原理是读取AE导出的配置json,然后用js操作svg API,动态创建元素并执行每一帧动画信息。lottie以其体积小,高还原度的优势,目前应用已经十分广泛流行。

安装

npm install lottie-web

使用

import lottie from 'lottie-web';
import * as animationData from "../test.json";
let myLottie = lottie.loadAnimation({
      container: this.$refs.lottieBox,
      renderer: "svg", // 渲染方式:svg、canvas
      loop: true, // 循环播放,默认:false
      autoplay: true, // 自动播放 ,默认true
      animationData: animationData.default // AE导出的lottie json数据 与path互斥
      // assetsPath: "" // json文件里资源的绝对路径,webpack项目需要配合这个参数
      // path:
      //   "https://labs.nearpod.com/bodymovin/demo/markus/halloween/markus.json" // json 路径
});
// myLottie.addEventListener("complete", lottieLoaded); // 动画播放完成触发 loop为false执行
   myLottie.addEventListener("loopComplete", lottieLoaded); // 当前循环播放完成触发 loop设置为true时执行

常用的事件监听

/**
 * 其他监听事件
 * enterFrame 播放一帧动画的时候触发
 * config_ready 初始配置完成
 * data_ready 所有动画数据加载完成
 * DOMLoaded 元素已添加到DOM节点
 * destroy 销毁
 * */

一些常用的方法

/**
 * lottie 全局方法
 * play(): 播放动画。
 * pause(): 暂停动画。
 * togglePause(): 切换播放和暂停的状态。
 * stop(): 停止动画。
 * goToAndStop(value: number, isFrame: boolean): 跳转到某时间/帧并播放。
 * goToAndPlay(value: number, isFrame: boolean): 跳转到某时间/帧并停止。
 * setSegment(init: number, end: number): 设置当前的segment区间。
 * playSegments(range: [number, number], force: boolean): 设置并播放当前的segment区间,force为true的时候讲立即播放,否则会等当前一循环播放完再切换。如果range[0] > range[1]则会设置方向为反播放。
 * resetSegments(force: boolean): 重置当前的segment区间,force依然是是否立即生效的标志。
 * setSpeed(value: number): 设置播放速度,基准为1。
 * setDirection(value: -1 | 1): 设置播放正反,1为正,-1为反。
 */

关于lottie的实现原理以及源码解析,不再本文描述了,这个哥们总结的也比较详细了:zhuanlan.zhihu.com/p/38140917