Appearance
绂诲睆 Canvas 涓庣紦瀛樹紭鍖?
棣栧厛瑕侀棶涓€涓棶棰橈細濡傛灉涓€涓鏉傚浘褰紙澶ч噺璺緞銆侀槾褰便€佹护闀滐級闇€瑕佹瘡甯ч噸缁橈紝浣嗗畠鏈韩寰堝皯鍙樺寲锛屾湁浠€涔堝姙娉曞姞閫燂紵
绛旀鏄細绂诲睆 Canvas 缂撳瓨銆傚氨鍍忛鍘呮彁鍓嶅仛濂藉崐鎴愬搧锛岀敤椁愭椂鍙渶鍔犵儹涓婅彍锛岃€屼笉鏄粠澶村紑濮嬪仛姣忛亾鑿溿€?
1. 绂诲睆 Canvas 姒傚康
浠€涔堟槸绂诲睆 Canvas锛?
绂诲睆 Canvas 鏄笉鏄剧ず鍦ㄩ〉闈笂鐨?Canvas 鍏冪礌锛岀敤浣滀复鏃剁粯鍒剁紦鍐插尯銆?
javascript
// 鍒涘缓绂诲睆 Canvas
const offscreen = document.createElement('canvas');
offscreen.width = 200;
offscreen.height = 200;
const offCtx = offscreen.getContext('2d');
// 鍦ㄧ灞?Canvas 涓婄粯鍒?
offCtx.fillStyle = 'red';
offCtx.fillRect(0, 0, 200, 200);
// 灏嗙灞?Canvas 缁樺埗鍒颁富 Canvas
mainCtx.drawImage(offscreen, 100, 100);鐜板湪鎴戣闂浜屼釜闂锛氳繖鏈変粈涔堢敤锛?
绛旀鏄細澶嶇敤銆傚鏉傜殑缁樺埗鎿嶄綔鍋氫竴娆★紝瀛樺湪绂诲睆 Canvas 涓紝涔嬪悗姣忔鍙渶瑕佸揩閫熺殑 drawImage 鎿嶄綔銆?
2. 涓轰粈涔堜娇鐢ㄧ灞?Canvas
鎬ц兘瀵规瘮
**鏈紦瀛?*锛氭瘡甯ч噸缁?
javascript
function render() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 澶嶆潅鐨勭粯鍒讹紙姣忓抚閮芥墽琛岋級
ctx.shadowColor = 'rgba(0,0,0,0.5)';
ctx.shadowBlur = 20;
ctx.shadowOffsetX = 5;
ctx.shadowOffsetY = 5;
for (let i = 0; i < 100; i++) {
ctx.fillStyle = `hsl(${i * 3.6}, 70%, 50%)`;
ctx.beginPath();
ctx.arc(x + i, y + i, 50 - i * 0.4, 0, Math.PI * 2);
ctx.fill();
}
}**宸茬紦瀛?*锛氶娓叉煋鍒扮灞?Canvas
javascript
// 鍙墽琛屼竴娆?
const complexShape = createComplexShape();
function render() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 蹇€熺粯鍒剁紦瀛樼殑鍥惧儚
ctx.drawImage(complexShape, x, y);
}
function createComplexShape() {
const offscreen = document.createElement('canvas');
offscreen.width = 200;
offscreen.height = 200;
const ctx = offscreen.getContext('2d');
ctx.shadowColor = 'rgba(0,0,0,0.5)';
ctx.shadowBlur = 20;
ctx.shadowOffsetX = 5;
ctx.shadowOffsetY = 5;
for (let i = 0; i < 100; i++) {
ctx.fillStyle = `hsl(${i * 3.6}, 70%, 50%)`;
ctx.beginPath();
ctx.arc(100 + i, 100 + i, 50 - i * 0.4, 0, Math.PI * 2);
ctx.fill();
}
return offscreen;
}鎬ц兘鎻愬崌锛?*10-100 鍊?*锛堝彇鍐充簬鍥惧舰澶嶆潅搴︼級銆?
3. 瀹炵幇棰勬覆鏌?
閫氱敤棰勬覆鏌撳嚱鏁?
javascript
function prerenderShape(width, height, drawFn) {
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
drawFn(ctx);
return canvas;
}
// 浣跨敤绀轰緥
const gradientCircle = prerenderShape(100, 100, (ctx) => {
const gradient = ctx.createRadialGradient(50, 50, 0, 50, 50, 50);
gradient.addColorStop(0, 'yellow');
gradient.addColorStop(1, 'red');
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.arc(50, 50, 50, 0, Math.PI * 2);
ctx.fill();
});
// 浣跨敤
ctx.drawImage(gradientCircle, x, y);楂?DPI 灞忓箷鏀寔
javascript
function prerenderShapeHiDPI(width, height, drawFn) {
const dpr = window.devicePixelRatio || 1;
const canvas = document.createElement('canvas');
canvas.width = width * dpr;
canvas.height = height * dpr;
canvas.style.width = `${width}px`;
canvas.style.height = `${height}px`;
const ctx = canvas.getContext('2d');
ctx.scale(dpr, dpr);
drawFn(ctx);
return canvas;
}4. 甯︾紦瀛樼殑鍥惧舰瀵硅薄
缂撳瓨澶辨晥鏈哄埗
绗笁涓棶棰橈細濡傛灉瀵硅薄灞炴€у彉鍖栦簡锛堝棰滆壊銆佸ぇ灏忥級锛岀紦瀛樻€庝箞鍔烇紵
绛旀鏄細**鑴忔爣璁?(Dirty Flag)**銆?
javascript
class CachedShape {
constructor(x, y, width, height, color) {
this.x = x;
this.y = y;
this._width = width;
this._height = height;
this._color = color;
this._cache = null;
this._cacheDirty = true; // 鍒濆鏍囪涓鸿剰
}
get width() {
return this._width;
}
set width(value) {
if (this._width !== value) {
this._width = value;
this._cacheDirty = true; // 鏍囪缂撳瓨澶辨晥
}
}
get height() {
return this._height;
}
set height(value) {
if (this._height !== value) {
this._height = value;
this._cacheDirty = true;
}
}
get color() {
return this._color;
}
set color(value) {
if (this._color !== value) {
this._color = value;
this._cacheDirty = true;
}
}
invalidateCache() {
this._cacheDirty = true;
}
updateCache() {
if (!this._cacheDirty) return; // 缂撳瓨鏈夋晥锛屾棤闇€鏇存柊
// 鍒涘缓鎴栬皟鏁寸紦瀛?Canvas 澶у皬
if (!this._cache ||
this._cache.width !== this._width ||
this._cache.height !== this._height) {
this._cache = document.createElement('canvas');
this._cache.width = this._width;
this._cache.height = this._height;
}
const ctx = this._cache.getContext('2d');
ctx.clearRect(0, 0, this._width, this._height);
// 缁樺埗鍒扮紦瀛?
this.renderToCache(ctx);
this._cacheDirty = false;
}
renderToCache(ctx) {
// 瀛愮被瀹炵幇鍏蜂綋缁樺埗
ctx.fillStyle = this._color;
ctx.fillRect(0, 0, this._width, this._height);
}
draw(mainCtx) {
this.updateCache(); // 鎸夐渶鏇存柊缂撳瓨
mainCtx.drawImage(this._cache, this.x, this.y);
}
}
// 浣跨敤
const shape = new CachedShape(100, 100, 200, 150, 'blue');
function render() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
shape.draw(ctx);
}
// 鏀瑰彉灞炴€ф椂锛岀紦瀛樿嚜鍔ㄥけ鏁堝苟閲嶅缓
shape.color = 'red'; // 涓嬫 draw() 鏃朵細閲嶆柊娓叉煋缂撳瓨
shape.width = 250; // 鍚屼笂5. 澶嶆潅鍥惧舰缂撳瓨
娓愬彉鍦嗙幆
javascript
class CachedGradientRing extends CachedShape {
constructor(x, y, radius, thickness, startColor, endColor) {
super(x, y, radius * 2, radius * 2);
this.radius = radius;
this.thickness = thickness;
this.startColor = startColor;
this.endColor = endColor;
}
renderToCache(ctx) {
const centerX = this.radius;
const centerY = this.radius;
// 鍒涘缓娓愬彉
const gradient = ctx.createRadialGradient(
centerX, centerY, this.radius - this.thickness,
centerX, centerY, this.radius
);
gradient.addColorStop(0, this.startColor);
gradient.addColorStop(1, this.endColor);
// 缁樺埗鍦嗙幆
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.arc(centerX, centerY, this.radius, 0, Math.PI * 2);
ctx.arc(centerX, centerY, this.radius - this.thickness, 0, Math.PI * 2, true);
ctx.fill();
}
}闃村奖鏂囧瓧
javascript
class CachedShadowText extends CachedShape {
constructor(x, y, text, fontSize, color) {
super(x, y, 200, 100); // 棰勪及澶у皬
this.text = text;
this.fontSize = fontSize;
this._color = color;
}
renderToCache(ctx) {
ctx.font = `${this.fontSize}px Arial`;
// 娴嬮噺鏂囨湰瀹藉害
const metrics = ctx.measureText(this.text);
this._width = metrics.width + 20;
this._height = this.fontSize + 20;
// 璋冩暣 Canvas 澶у皬
if (this._cache.width !== this._width || this._cache.height !== this._height) {
this._cache.width = this._width;
this._cache.height = this._height;
ctx.font = `${this.fontSize}px Arial`; // 閲嶆柊璁剧疆瀛椾綋
}
// 缁樺埗闃村奖
ctx.shadowColor = 'rgba(0,0,0,0.5)';
ctx.shadowBlur = 10;
ctx.shadowOffsetX = 3;
ctx.shadowOffsetY = 3;
ctx.fillStyle = this._color;
ctx.textBaseline = 'top';
ctx.fillText(this.text, 10, 10);
}
}6. OffscreenCanvas API
本章小结
本章介绍了离屏 Canvas 的基础概念:
核心概念:
- 离屏 Canvas 作为缓存缓冲区
- 复杂图形预渲染,多次复用
- 10-100倍性能提升
实现方式:
- 通用预渲染函数
- 带缓存失效的图形对象
- 脏标记机制
应用场景:
- 复杂图形缓存
- 渐变和阴影效果
- 重复使用的图形元素
在下一章,我们将学习 OffscreenCanvas API 和高级缓存管理策略。