360前端星计划---前端工程化/动画/优化

215 阅读7分钟

前端工程化

  • 目标:利用技术进步&经验积累带来的各种方案,解决项目中开发,测试,维护中遇到的各种低效和繁琐的问题
  • 技术:不断演变和进步,服务于工程化思想
  • 原因:提效,提高生产力,体现在项目的开发,测试和维护阶段

规范化

git flow 规范

  • master 主干分支,上线的代码
  • develop 开发分支,
  • hotfix 紧急修复bug
  • release 是上线之前的临时分支
  • feature 新增功能,是从develop临时复制的一个分支,开发完合并

相关操作

初始化

git flow init 自动生成master develop分支
git branch
git checkout develop

开发feature

git pull origin develop
git flow feature start f1
# git checkout develop 
# git checkout -b feature/f1 这两条是上面flow feature start 的简写
git commit -am 'commit'
git push origin feature/f1 或者 git flow feature publish f1

git checkout feature/f1
git flow feature finish f1
# git checkout out develop
# git merge feature/f1 同样是上面的简写,现在在develop分支上

git push origin develop

上线

上线要先推送到release分支进行检查

git checkout develop
git flow release start 0.01
git commit -am "commit"
git push origian release/v0.0.1

检查无误,正式上线。上线的版本会同时合并到masterand develop

git checkout master
git pull origin master
git checkout release/0.0.1
git flow release finish 0.0.1
# git checkout develop 上面这一条同时进行了下面的全部动作
# git merge release/0.0.1
# git checkout master
# git merge release/0.0.1
# git tag 0.0.1
# git branch -D release/0.0.1
git push origin master
git push origin 0.0.1

hotfix

hotfix存在的意义是减少流程,快速上线

git checkout master
git flow hotfix start fix1
# git checkout master
# git checkout -b hotfix/fix1
git flow hotfix finish fix1 同时合并到`master`and `develop`

模块化

前端中一般是逻辑相关的代码放在一个文件,当作一个模块。只要关注模块内逻辑的实现,无需考虑变量污染的问题,模块之间可以相互调用。

CSS模块化

核心思想:样式生效规则来避免冲突 有四种基本的方案

  • scoped: DOM节点添加data-v-version属性,.selector=>.selector[data-v-version]
  • Css in JS: 以脚本模块来写样式,甚至可以封装常用样式。可以按规则生成唯一的selector
  • CSS MODULES: 借助预编译使样式成为脚本中的变量
  • BEM Block_ELement-Modifier 是一种命名规范。或者借助SASS
  • shadow root:比较现代的方法,兼容性堪忧

JS模块化

  • CommonJS
  • ES6 module

组件化

模块化和组件化的核心思想都在于分治,好处就是团队协作效率和项目可维护性的提升。组件可以分为Presentationalcontainer组件,分别用于展示UI和维护逻辑,方便复用。UI组件一般会包含逻辑组件。

自动化(重点与核心)

  • 自动初始化 vue-cli
  • 自动构建打包 webpack
  • 自动测试 karma,jest
  • 自动部署 Jenkins

自动化测试

从上到下和逻辑关系越来越紧密

JS动画

  • 定时器改变对象的属性
  • 根据新的属性重新渲染动画
  • 优点(相对CSS/SVG动画) 灵活性,可控性,性能
  • 缺点 易用性

通用化

function update({target},count){
    target.style.tranform = `rotate(${count++}deg)`;
}
class Ticker{
    tick(update,context){
        let count = 0;
        requestAnimationFrame(function next(){
            if(update(context,++count) !== false){
                requestAnimationFrame(next);
            }
        })
    }
}
const ticker = new Ticker();
ticker.tick(update,{target:block});

这个版本里面实现了简易旋转的动画,但是要精细的控制时间,可以使用下面这个版本,做到了恒速旋转。

function update({target}, {time}) {
  target.style.transform = `rotate(${360 * time / 2000}deg)`;
}

class Ticker {
  tick(update, context) {
    let count = 0;
    let startTime = Date.now();
    requestAnimationFrame(function next() {
      count++;
      const time = Date.now() - startTime;
      if(update(context, {count, time}) !== false) {
        requestAnimationFrame(next);
      }
    });
  }
}

const ticker = new Ticker();
ticker.tick(update, {target: block});

上面两个例子都是直接操作DOM的style,那么如何使用H5的新特性 canvas

function update({context}, {time}) {
  context.clearRect(0, 0, 512, 512);
  context.save();
  context.translate(100, 100);
  context.rotate(time * 0.005);
  context.fillStyle = '#00f';
  context.fillRect(-50, -50, 100, 100);
  context.restore();
}

class Ticker {
  tick(update, context) {
    let count = 0;
    let startTime = Date.now();
    requestAnimationFrame(function next() {
      count++;
      const time = Date.now() - startTime;
      if(update(context, {count, time}) !== false) {
        requestAnimationFrame(next);
      }
    });
  }
}

一个更强大的Timing控制器

class Timing {
  constructor({duration, easing} = {}) {
    this.startTime = Date.now();
    this.duration = duration;
    this.easing = easing || function(p){return p};
  }
  get time() {
    return Date.now() - this.startTime;
  }
  get p() {
    return this.easing(Math.min(this.time / this.duration, 1.0));
  }
}

class Ticker {
  tick(update, context, timing) {
    let count = 0;
    timing = new Timing(timing);
    requestAnimationFrame(function next() {
      count++;
      if(update(context, {count, timing}) !== false) {
        requestAnimationFrame(next);
      }
    });
  }
}

匀速运动

比如在两秒内匀速运动200px, 那么timing的duration参数就是2000, 每次更新 translate 200*timing.p 就可以

自由落地运动

根据公式,我们知道距离和时间成平方关系,200*timing.p的200这个时候就变成了a/2,timing.p要有一个平方的映射,可以用easing来处理

const ticker = new Ticker();
ticker.tick(update, {target: block}, {
  duration: 2000,
  easing: p => p ** 2,
});

摩擦力

easing可以有一个x(T-x)的映射

平抛

同时考虑x,y两个轴上的运动,translate要在两个参数上都设置

椭圆轨迹

x = a.cost(a)
y = b.sin(a)

来控制translate的x,y方向的位移。

周期运动

加上iterations参数控制周期数,使得timing.p始终在0-1之间重复iterations次

class Timing {
  constructor({duration, easing, iterations = 1} = {}) {
    this.startTime = Date.now();
    this.duration = duration;
    this.easing = easing || function(p){return p};
    this.iterations = iterations;
  }
  get time() {
    return Date.now() - this.startTime;
  }
  get finished() {
    return this.time / this.duration >= 1.0 * this.iterations;
  }
  get op() {
    let op = Math.min(this.time / this.duration, 1.0 * this.iterations);
    if(op < 1.0) return op;
    op -= Math.floor(op);
    return op > 0 ? op : 1.0;
  }
  get p() {
    return this.easing(this.op);
  }
}

连续运动

class Ticker {
  tick(update, context, timing) {
    let count = 0;
    timing = new Timing(timing);
    return new Promise((resolve) => {
      requestAnimationFrame(function next() {
        count++;
        if(update(context, {count, timing}) !== false && !timing.finished) {
          requestAnimationFrame(next);
        } else {
          resolve(timing);
        }
      });      
    });
  }
}

使用promise的callback处理周期,直到时间到了,才resolve promise使其从callback中跳出来变成fulfill状态

线性插值

通过这个公式变换,我们可以使用一个lerp函数对值进行操作,然后setter到目标里面。

function lerp(setter, from, to) {
  return function({target}, {timing}) {
    const p = timing.p;
    const value = {};
    for(let key in to) {
      value[key] = to[key] * p + from[key] * (1 - p);
    }
    setter(target, value);
  }
}

弹跳的小球

const down = lerp(setValue, {top: 100}, {top: 300});
const up = lerp(setValue, {top: 300}, {top: 100});

(async function() {
  const ticker = new Ticker();
  
  // noprotect
  while(1) {
    await ticker.tick(down, {target: block},
      {duration: 2000, easing: p => p * p});
    await ticker.tick(up, {target: block},
      {duration: 2000, easing: p => p * (2 - p)});
  }
})();

平稳变速

function forward(target, {y}) {
  target.style.top = `${y}px`;
}

(async function() {
  const ticker = new Ticker();

  await ticker.tick(
    lerp(forward, {y: 100}, {y: 200}), 
    {target: block},
    {duration: 2000, easing: p => p * p}); 

  await ticker.tick(
    lerp(forward, {y: 200}, {y: 300}), 
    {target: block},
    {duration: 1000, easing: p => p}); 

  await ticker.tick(
    lerp(forward, {y: 300}, {y: 350}), 
    {target: block},
    {duration: 1000, easing: p => p * (2 - p)}); 
}());

逐帧动画

<style type="text/css">
.sprite {
  display:inline-block; 
  overflow:hidden; 
  background-repeat: no-repeat;
  background-image:url(https://p.ssl.qhimg.com/t01f265b6b6479fffc4.png);
}

.bird0 {width:86px; height:60px; background-position: -178px -2px}
.bird1 {width:86px; height:60px; background-position: -90px -2px}
.bird2 {width:86px; height:60px; background-position: -2px -2px}

 #bird{
   position: absolute;
   left: 100px;
   top: 100px;
   zoom: 0.5;
 }
</style>
<div id="bird" class="sprite bird1"></div>
<script type="text/javascript">
var i = 0;
setInterval(function(){
  bird.className = "sprite " + 'bird' + ((i++) % 3);
}, 1000/10);
</script>

Web Animation API(Working Draft)
element.animate(keyframes, options);
target.animate([
  {backgroundColor: '#00f', width: '100px', height: '100px', borderRadius: '0'},
  {backgroundColor: '#0a0', width: '200px', height: '100px', borderRadius: '0'},
  {backgroundColor: '#f0f', width: '200px', height: '200px', borderRadius: '100px'},
], {
  duration: 5000,
  fill: 'forwards',
});

Web Animation API

对一个元素直接调用 animate方法,传入关键帧,比如from,50%,to; duration和fill可以表示时间和结束时动画状态。很简单但是要详细控制还是要使用之前介绍的方法。

前端性能优化

RAIL模型

Response

  • 要把延迟控制在100ms内最好,1s之内是正常的。
  • 50ms内处理用户输入事件,确保100ms内反馈用户可视的响应
  • 开销大的任务可分割任务处理,或者放到worker进程中执行,避免影响用户交互
  • 超过50ms的操作,始终给予反馈(进度条)

Animation

  • 10ms或者更短时间内生成1帧,因为渲染要大概6ms左右
  • 要考虑视觉平滑
  • 处理动画逻辑尽可能的少

Idle

  • 要最大化空闲时间以增加页面在100ms内响应用户输入的几率
  • 利用空闲时间完成推迟的工资
  • 即使要加载一些任务,用户可能的交互要优先考虑

Load

  • 首屏时间对于普通中档设备应当在5s内呈现交互内容
  • 非首屏加载应该在2s内完成
  • 优化关键渲染路径以解除阻止渲染
  • 影响加载性能的因素:网络/CPU/解析JS

工具篇

  • lighthouse
  • window.performancetiming
  • webpagetest
  • chrome devtools

实战篇

浏览器渲染场景

  • JavaScript 实现动画/操作DOM
  • Style(Render Tree)
  • Layout 盒模型,确切的位置和大小
  • Paint 栅格化
  • Composite 渲染层合并 有些属性会触发Layout和Paint,有些则不会。csstriggers.com可以看到详细信息。

个人的优化总结