核心需求 避免變量污染前端
對於UI和邏輯的封裝webpack
將重複費時的操做交給工具git
通用的抽象接口 線性++:web
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});
通用的抽象接口 角度 時間:chrome
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})
通用的接口 canvas: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); } }); } }
後面的具體例子就是數學公式結合前面的通用接口,找幾個例子來記一下promise
貝塞爾軌跡:瀏覽器
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), });
週期運動:實際上週期運動的timing發生了變化性能優化
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); } }
用promise來寫連續運動Ticker 其實目標就是使用promise作流程控制
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 trick的應用 小球彈跳
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)}); } })();
RAIL模型
關鍵指標:
針對瀏覽器渲染流程:頁面是怎麼樣展現出來的?
JS->Style(這一步的輸出是渲染樹)->Layout(計算 產出盒模型)->paint(柵格化)->Composite(渲染層合併)
Layout 和 paint 是能夠不出現的 因此會有各類場景
加載優化
渲染優化