360前端星计划学习笔记(七)前端工程化浅析–田东东老师
360前端星计划学习笔记(八)前端动画
目录
前端工程化
什么是前端工程化?
工程化:
-
目标:方案,开发各个阶段,解决低效问题
-
技术:工程化是一种思想,技术是一种实践,思想的落地离不开行动
-
原因:为了提效,提效体现在项目的开发、测试及维护阶段
规范化、模块化、组件化、自动化
规范化(项目可维护性的基石)
版本管理及开发流程规范
编写规范
- 脚本
- 样式
- 目录结构
git 版本管理,代码仓库
git flow 基于Git、简化操作,活动模型、行为规范
Hotfix 紧急修复
Master 上线的分支
Release
Develop 开发分支
Feature
git flow init
git checkout develop
git pull origin develop
git flow feature start f1
#git checkout develop
#git checkout -b feature/f1
git commit -am "add#"
git push origin feature/f1
git push origin develop
git flow release start 0.0.1
git flow release finish 0.0.1 同时合并到master和develop,并且打tag
git flow hotfix start fix1 //master 和develop同步
参考资料:https://git-scm.com/docs
https://www.atmarkit.co.jp/ait/articles/1708/01/news015.html
https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow
https://danielkummer.github.io/git-flow-cheatsheet/
模拟一次从开发到上线打tag的开发过程
模块化
一般逻辑相关的代码放在同一个文件中,当做一个模块,只需关注模块内逻辑的实现,无需考虑变量污染
CSS模块化
通过样式生效规则来避免冲突
scoped DOM节点添加data-v-version属性 eg: https://juejin.im/
CSS in JS React 用的较多,styled-components
以脚本模块来写样式,甚至有封装好的样式模块可以直接调用
样式=》编译成唯一的sector eg.https://codepen.io
CSS Modules: 借助预编译使样式变成脚本变量
BEM Block_Element-Modifier 按照规则,手写css,并在模板内增加相应class
一般用sass嵌套编译后可实现,写方便,看也方便
eg.elementUI
CSS模块化解决方案
为元素建立shadow root 内部样式与外部样式完全隔离 ,目前支持不多
JS模块化解决方案
CommonJS规范 module.exports=xx;
ES Module-loader: export functon M(){} import {M} from “/a.js”
组件化
核心思想在于分治,好处:团队协作效率和可维护性的提升。
Vue,React ,Webcomponent
什么是组件?
1、UI为主
2、逻辑为主
定义:在Web前端领域,我们可以将由特定逻辑和UI进行的高内聚,低耦合的封装体。
如何基于组件化思想,如何重构页面
自动化
自动初始化:vue-cli
自动化构建:webpack
自动化测试:karma,jest
自动化部署,Jenkins
自动化测试:单元测试,集成测试,端到端测试,UI测试(写好单元测试用例)
自动化部署:git push web hook/poll ;gitlab 可持续集成
自动初始化:使用nodejs写脚手架
如何捕获用户输入的参数和命令
用 commander库
如何触发询问与用户交互
inquirer库
如何帮我们执行命令
child_process
如何增强交互效果
chalk,ora
自动化构建
webpack,rulg,parcel
使用webpack4进行项目构建
将所有内容作为模块
核心配置:
- mode:开发模式,development,production
- entry入口
- outout 输出
- module:{rules:[]}模块和定义规则
- plugins:[]插件
webpack配置建议:不同环境的配置区分开,集成进来的工具的插件配置单独放置,env配置使用.browserslistrc文件单独放置
前端:遵守规范,要有工程化,自动化的思想
JS动画的原理和基本实现
使用定时器改变对象属性
根据新的属性重新渲染动画
动画的种类
JavaScript 动画
-
操作DOM
-
canvas
优点:灵活度,可控性,性能缺点:易用性
CSS 动画
SVG 动画
SMIL
let rotation=0;
requestAnimationFram(function update(){
block.style.transform=`rotate(${rotation+=15}deg)`
requestAnimationFrame(update)
})
简单,但精确控制比较难
改进1
let rotation = 0;
let startTime = null;
const T = 2000;
requestAnimationFrame(function update() {
if(!startTime) startTime = Date.now();
const p = (Date.now() - startTime)/T;
block.style.transform = `rotate(${360 * p}deg)`;
requestAnimationFrame(update);
});
设置周期 轨迹动画等
通用化
function update({target}, count) {
target.style.transform = `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});
通用化2
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});
通用化3-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);
}
});
}
}
匀速运动
2s 200px
function update({target}, {timing}) {
target.style.transform = `translate(${200 * timing.p}px, 0)`;
}
const ticker = new Ticker();
ticker.tick(update,
{target: block},
{duration: 2000}
);
自由落体运动
function update({target}, {timing}) {
target.style.transform = `translate(0, ${200 * timing.p}px)`;
}
const ticker = new Ticker();
ticker.tick(update, {target: block}, {
duration: 2000,
easing: p => p ** 2,
});
匀减速运动
easing p*(2-p)
function update({target}, {timing}) {
target.style.transform = `translate(${200 * timing.p}px, 0)`;
}
const ticker = new Ticker();
ticker.tick(update, {target: block}, {
duration: 2000,
easing: p => p * (2 - p),
});
平抛
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 op() {
return Math.min(this.time / this.duration, 1.0);
}
get p() {
return this.easing(this.op);
}
}
function update({target}, {timing}) {
target.style.transform =
`translate(${200 * timing.op}px, ${200 * timing.p}px)`;
}
旋转+平抛
function update({target}, {timing}) {
target.style.transform = `
translate(${200 * timing.op}px, ${200 * timing.p}px)
rotate(${720 * timing.op}deg)
`;
}
贝塞尔轨迹
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iekAF3uq-1586614901735)(360-learn-note-8/image-20200411161304394.png)]
function bezierPath(x1, y1, x2, y2, p) {
const x = 3 * x1 * p * (1 - p) ** 2 + 3 * x2 * p ** 2 * (1 - p) + p ** 3;
const y = 3 * y1 * p * (1 - p) ** 2 + 3 * y2 * p ** 2 * (1 - p) + p ** 3;
return [x, y];
}
function update({target}, {timing}) {
const [px, py] = bezierPath(0.2, 0.6, 0.8, 0.2, timing.p);
target.style.transform = `translate(${100 * px}px, ${100 * py}px)`;
}
const ticker = new Ticker();
ticker.tick(update, {target: block}, {
duration: 2000,
easing: p => p * (2 - p),
});
bezier-easing
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iUoQdPtn-1586614901738)(360-learn-note-8/image-20200411161453326.png)]
function update({target}, {timing}) {
target.style.transform = `translate(${100 * timing.p}px, 0)`;
}
const ticker = new Ticker();
ticker.tick(update, {target: block}, {
duration: 2000,
easing: BezierEasing(0.5, -1.5, 0.5, 2.5),
});
使用bazier-easing的库
bazier-easing轨迹
function update({target}, {timing}) {
target.style.transform =
`translate(${100 * timing.p}px, ${100 * timing.op}px)`;
}
const ticker = new Ticker();
ticker.tick(update, {target: block}, {
duration: 2000,
easing: BezierEasing(0.5, -1.5, 0.5, 2.5),
});
椭圆轨迹
使用参数方式改变alpha
周期运动
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);
}
}
椭圆周期运动
function update({target}, {timing}) {
const x = 150 * Math.cos(Math.PI * 2 * timing.p);
const y = 100 * Math.sin(Math.PI * 2 * timing.p);
target.style.transform = `
translate(${x}px, ${y}px)
`;
}
const ticker = new Ticker();
ticker.tick(update, {target: block},
{duration: 2000, iterations: 10});
连续运动
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);
}
});
});
}
}
function left({target}, {timing}) {
target.style.left = `${100 + 200 * timing.p}px`;
}
function down({target}, {timing}) {
target.style.top = `${100 + 200 * timing.p}px`;
}
function right({target}, {timing}) {
target.style.left = `${300 - 200 * timing.p}px`;
}
function up({target}, {timing}) {
target.style.top = `${300 - 200 * timing.p}px`;
}
(async function() {
const ticker = new Ticker();
await ticker.tick(left, {target: block},
{duration: 2000});
await ticker.tick(down, {target: block},
{duration: 2000});
await ticker.tick(right, {target: block},
{duration: 2000});
await ticker.tick(up, {target: block},
{duration: 2000});
})();
线性插值
f ( p ) = f r o m + ( t o − f r o m ) ∗ p f(p)=from +(to-from)*p f(p)=from+(to−from)∗p
f ( p ) = t o ∗ p + f r o m ∗ ( 1 − p ) f(p)=to*p+from*(1-p) f(p)=to∗p+from∗(1−p)
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);
}
}
function setValue(target, value) {
for(let key in value) {
target.style[key] = `${value[key]}px`;
}
}
const left = lerp(setValue, {left: 100}, {left: 300});
const down = lerp(setValue, {top: 100}, {top: 300});
const right = lerp(setValue, {left: 300}, {left: 100});
const up = lerp(setValue, {top: 300}, {top: 100});
(async function() {
const ticker = new Ticker();
await ticker.tick(left, {target: block},
{duration: 2000});
await ticker.tick(down, {target: block},
{duration: 2000});
await ticker.tick(right, {target: block},
{duration: 2000});
await ticker.tick(up, {target: block},
{duration: 2000});
})();
弹跳的小球
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)});
}
})();
弹跳的小球(含衰减)
(async function() {
const ticker = new Ticker();
let damping = 0.7,
duration = 2000,
height = 300;
// noprotect
while(height >= 1) {
let down = lerp(setValue, {top: 400 - height}, {top: 400});
await ticker.tick(down, {target: block},
{duration, easing: p => p * p});
height *= damping ** 2;
duration *= damping;
let up = lerp(setValue, {top: 400}, {top: 400 - height});
await ticker.tick(up, {target: block},
{duration, easing: p => p * (2 - p)});
}
})();
滚动
const roll = lerp((target, {left, rotate}) => {
target.style.left = `${left}px`;
target.style.transform = `rotate(${rotate}deg)`;
},
{left: 100, rotate: 0},
{left: 414, rotate: 720});
const ticker = new Ticker();
ticker.tick(roll, {target: block},
{duration: 2000, easing: p => 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)});
}());
甩球
function circle({target}, {timing}) {
const p = timing.p;
const rad = Math.PI * 2 * p;
const x = 200 + 100 * Math.cos(rad);
const y = 200 + 100 * Math.sin(rad);
target.style.left = `${x}px`;
target.style.top = `${y}px`;
}
function shoot({target}, {timing}) {
const p = timing.p;
const rad = Math.PI * 0.2;
const startX = 200 + 100 * Math.cos(rad);
const startY = 200 + 100 * Math.sin(rad);
const vX = -100 * Math.PI * 2 * Math.sin(rad);
const vY = 100 * Math.PI * 2 * Math.cos(rad);
const x = startX + vX * p;
const y = startY + vY * p;
target.style.left = `${x}px`;
target.style.top = `${y}px`;
}
(async function() {
const ticker = new Ticker();
await ticker.tick(circle, {target: block},
{duration: 2000, easing: p => p, iterations: 2.1});
await ticker.tick(shoot, {target: block},
{duration: 2000});
}());
逐帧动画
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title></title>
</head>
<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>
<body>
<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>
</body>
</html>
Web Animation API
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',
});
function animate(target, keyframes, options) {
const anim = target.animate(keyframes, options);
return new Promise((resolve) => {
anim.onfinish = function() {
resolve(anim);
}
});
}
(async function() {
await animate(ball1, [
{top: '10px'},
{top: '150px'},
], {
duration: 2000,
easing: 'ease-in-out',
fill: 'forwards',
});
await animate(ball2, [
{top: '200px'},
{top: '350px'},
], {
duration: 2000,
easing: 'ease-in-out',
fill: 'forwards',
});
await animate(ball3, [
{top: '400px'},
{top: '550px'},
], {
duration: 2000,
easing: 'ease-in-out',
fill: 'forwards',
})
总结
javascript 动画的方式:
增量的方式
通过时间的方式
连续动画使用Promise封装后,通过await调用
前端性能优化
RAIL模型
定义
以用户为中心,每个网络应用都具有与其生命周期有关的四个方面
指导意见:推荐的性能评估标准,目标:
主要的四个方面:Response,Animation,Idle,Load
延迟与用户反应,感知
指导意见:
-
50ms内处理用户输入事件,确保100ms反馈用户可视的响应
-
对于开销大的任务可分割任务处理,或放到worker进程中执行,避免影响用户交互
-
处理时间超过50ms的操作,始终给予反馈(进度和活动指示器)
动画:10ms 一帧
1000ms/60帧
空闲时间最大化
目标:最大化空闲时间以增加页面在100ms内响应用户输入的几率
指导:利用空闲时间完成推迟的工作
空闲时间期间用户交互优先级最高
5s 呈现交互内容
总结:
响应:在100ms内响应用户输入
动画:动画或滚动时,10ms产生一帧
空闲时间:主线程空闲时间最大化
加载:在1000ms内呈现交互内容
以用户为中心
工具篇
Lighthouse
google Audit 提供优化建议
WebPageTest
提交服务地址,发报告给我们
Chrome DevTools
实战篇
浏览器渲染场景
查看每个样式所影响的范围
浏览器渲染流程
JavaScript (实现动画,操作DOM等)
Style(Render Tree)
Layout(盒模型,确切的位置和大小)
Paint(栅格化)
Composite(渲染层合并)
设置浏览器CPU限制
可以录制火焰图
Chrome
console
elements 元素布局
sources
network
Performance性能分析
Application
Audits 审计
性能指标的评分
如何分析网站性能
Performance 截图
录制过程中,生成火焰图,打开Main,
点击红色部分,下面会在Summary中列出任务发生的具体情况,链接到原文件位置,发现性能瓶颈
尽量不要在设置样式之后读取样式属性
const offsetTop=parseInt(m.style.top.slice(0,-2))
代替所有的m.offsetTop
减少触发强制布局,
深度优化:细节执行的问题
将样式设置方式修改
m.style.top =pos +“px”
替换为 m.style.transform =translateY(${post-m.style.top.slice(0,-2)}px)
性能优化的方向
加载
- 资源效率优化,压缩,缓存
- 图片优化
- 字体优化
- 关键渲染路径优化
渲染
- javaScirpt执行优化
- 避免大型复杂的布局
- 渲染层合并