Lý lịch trích ngang của tôi
Tôi biết đến <canvas>
vào năm 2006 khi Firefox phiên bản 2.0 được phát hành. Một bài viết trên Ajaxian, mô tả ma trận biến đổi, đã truyền cảm hứng cho tôi tạo ứng dụng web <canvas>
đầu tiên; Color Sphere (2007). Điều này đã đưa tôi vào thế giới màu sắc và các đối tượng đồ hoạ nguyên thuỷ; truyền cảm hứng cho việc tạo ra Sketchpad (2007-2008) nhằm tạo ra một ứng dụng "tốt hơn Paint" trong trình duyệt.
Những thử nghiệm này cuối cùng đã dẫn đến việc thành lập công ty khởi nghiệp Mugtug cùng với bạn bè lâu năm của tôi là Charles Pritchard. Chúng tôi đang phát triển Darkroom trong HTML5 <canvas>
. Darkroom là một ứng dụng chia sẻ ảnh không làm hỏng ảnh, kết hợp sức mạnh của các bộ lọc dựa trên pixel với kiểu chữ và bản vẽ dựa trên vectơ.
Giới thiệu

<canvas>
mang đến cho lập trình viên Javascript toàn quyền kiểm soát màu sắc, vectơ và pixel trên màn hình – thành phần hình ảnh của màn hình.
Các ví dụ sau đây xử lý một phần trong <canvas>
chưa được chú ý nhiều; tạo hiệu ứng văn bản. Bạn có thể tạo nhiều hiệu ứng văn bản trong <canvas>
như bạn có thể tưởng tượng – các bản minh hoạ này chỉ đề cập đến một phần nhỏ những hiệu ứng có thể tạo được. Mặc dù chúng ta đang xử lý "văn bản" trong bài viết này, nhưng các phương thức này có thể được áp dụng cho bất kỳ đối tượng vectơ nào; tạo hình ảnh thú vị trong trò chơi và các ứng dụng khác:
- Text-Shadows trong
<canvas>
. - Các hiệu ứng văn bản giống như CSS trong
<canvas>
tạo mặt nạ cắt, tìm chỉ số trong<canvas>
và sử dụng thuộc tính bóng. - Cầu vồng neon, phản chiếu sọc – hiệu ứng tạo chuỗi.
- Các hiệu ứng văn bản giống như Photoshop trong
<canvas>
ví dụ về cách sử dụng globalCompositeOperation, createLinearGradient, createPattern. - Bóng trong và bóng ngoài trong
<canvas>
- Hiện một tính năng ít được biết đến; sử dụng chiều kim đồng hồ so với chiều kim đồng hồ ngược để tạo bóng đổ ngược (bóng đổ bên trong).
- Spaceage – hiệu ứng tạo sinh. Hiệu ứng văn bản
- dựa trên tính năng tạo sinh trong
<canvas>
sử dụng chu kỳ màu hsl() vàwindow.requestAnimationFrame
để tạo cảm giác chuyển động.
Bóng văn bản trong Canvas
Một trong những tính năng bổ sung mà tôi yêu thích nhất trong thông số kỹ thuật CSS3 (cùng với border-radius, web-gradients và các tính năng khác) là khả năng tạo bóng. Điều quan trọng là bạn phải nhận ra sự khác biệt giữa bóng CSS và <canvas>
, cụ thể:
CSS sử dụng hai phương thức; box-shadow cho các phần tử hộp, chẳng hạn như div, span, v.v.; và text-shadow cho nội dung văn bản.
<canvas>
có một loại bóng; loại bóng này được dùng cho tất cả các đối tượng vectơ; ctx.moveTo, ctx.lineTo, ctx.bezierCurveTo, ctx.quadradicCurveTo, ctx.arc, ctx.rect, ctx.fillText, ctx.strokeText, v.v.
Để tạo bóng trong <canvas>
, hãy khai thác 4 thuộc tính sau:
- ctx.shadowColor = "red" // string
- Màu của bóng; RGB, RGBA, HSL, HEX và các giá trị đầu vào khác đều hợp lệ.
- ctx.shadowOffsetX = 0; // số nguyên
- Khoảng cách theo chiều ngang của bóng, so với văn bản.
- ctx.shadowOffsetY = 0; // số nguyên
- Khoảng cách theo chiều dọc của bóng, so với văn bản.
- ctx.shadowBlur = 10; // số nguyên
- Hiệu ứng làm mờ cho bóng, giá trị càng lớn thì độ mờ càng lớn.
Để bắt đầu, hãy xem cách <canvas>
có thể mô phỏng các hiệu ứng CSS.
Khi tìm kiếm "css text-shadow" trên Google Images, chúng ta sẽ thấy một số bản minh hoạ tuyệt vời để mô phỏng; Line25, Stereoscopic và Shadow 3D.

Hiệu ứng 3D lập thể (xem Hình ảnh anaglyph để biết thêm thông tin) là ví dụ về một dòng mã đơn giản, được sử dụng hiệu quả. Với dòng CSS sau đây, chúng ta có thể tạo ảo giác về chiều sâu khi xem bằng kính 3D màu đỏ/xanh lục lam (loại kính mà họ cung cấp cho bạn tại rạp chiếu phim 3D):
text-shadow: -0.06em 0 0 red, 0.06em 0 0 cyan;
Có hai điều cần lưu ý khi chuyển đổi chuỗi này thành <canvas>
:
- Không có shadow-blur (giá trị thứ ba), vì vậy, không có lý do gì để thực sự chạy shadow, vì fillText sẽ tạo ra kết quả tương tự:
var text = "Hello world!"
ctx.fillStyle = "#000"
ctx.fillText(text, -7, 0);
ctx.fillStyle = "red"
ctx.fillText(text, 0, 0);
ctx.fillStyle = "cyan"
ctx.fillText(text, 7, 0);</pre>
- EM không được hỗ trợ trong
<canvas>
, vì vậy, bạn sẽ phải chuyển đổi các EM thành PX. Chúng ta có thể tìm tỷ lệ chuyển đổi để chuyển đổi giữa PT, PC, EM, EX, PX, v.v. bằng cách tạo một phần tử có cùng thuộc tính phông chữ trong DOM và đặt chiều rộng thành định dạng cần đo lường; hoặc ví dụ: để ghi lại lượt chuyển đổi EM -> PX, chúng ta sẽ đo lường phần tử DOM bằng "height: 1em", offsetHeight thu được sẽ là số PX trong mỗi EM.
var font = "20px sans-serif"
var d = document.createElement("span");
d.style.cssText = "font: " + font + " height: 1em; display: block"
// the value to multiply PX 's by to convert to EM 's
var EM2PX = 1 / d.offsetHeight;</pre>
Ngăn chặn việc nhân alpha
Trong một ví dụ phức tạp hơn, chẳng hạn như hiệu ứng Neon trên Line25, bạn phải sử dụng thuộc tính shadowBlur để mô phỏng hiệu ứng đúng cách. Vì hiệu ứng Neon dựa vào nhiều bóng nên chúng ta gặp phải vấn đề; trong <canvas>
, mỗi đối tượng vectơ chỉ có thể có một bóng. Vì vậy, để vẽ nhiều bóng, bạn phải vẽ nhiều phiên bản văn bản chồng lên nhau.
Điều này dẫn đến việc nhân alpha và cuối cùng là các cạnh bị răng cưa.

Tôi đã thử chạy ctx.fillStyle = "rgba(0,0,0,0)"
hoặc "transparent"
để ẩn văn bản, trong khi hiển thị bóng… tuy nhiên, nỗ lực này là vô ích; vì bóng là phép nhân của alpha fillStyle, nên bóng không bao giờ có thể mờ hơn fillStyle.
May mắn thay, có một cách để giải quyết vấn đề này. Chúng ta có thể vẽ độ lệch bóng từ văn bản, tách biệt chúng (để không chồng chéo) và do đó ẩn văn bản ở cạnh màn hình:
var text = "Hello world!"
var blur = 10;
var width = ctx.measureText(text).width + blur * 2;
ctx.textBaseline = "top"
ctx.shadowColor = "#000"
ctx.shadowOffsetX = width;
ctx.shadowOffsetY = 0;
ctx.shadowBlur = blur;
ctx.fillText(text, -width, 0);
Cắt xung quanh một khối văn bản
Để dọn dẹp một chút, chúng ta có thể ngăn việc vẽ fillText ở vị trí đầu tiên (trong khi vẫn cho phép vẽ bóng) bằng cách thêm một đường cắt.
Để tạo đường cắt xung quanh văn bản, chúng ta cần biết chiều cao của văn bản (được gọi là "chiều cao em", trước đây là chiều cao của chữ cái "M" trên máy in) và chiều rộng của văn bản.
Chúng ta có thể lấy chiều rộng bằng ctx.measureText().width
, tuy nhiên, ctx.measureText().height
không tồn tại.
May mắn thay, thông qua các thủ thuật CSS (xem Chỉ số kiểu chữ để biết thêm các cách khắc phục việc triển khai <canvas>
cũ bằng cách sử dụng các phép đo CSS), chúng ta có thể tìm thấy chiều cao của văn bản thông qua việc đo lường offsetHeight
của <span>
có cùng thuộc tính phông chữ:
var d = document.createElement("span");
d.font = "20px arial"
d.textContent = "Hello world!"
var emHeight = d.offsetHeight;
Từ đó, chúng ta có thể tạo một hình chữ nhật để dùng làm đường cắt; bao quanh "bóng" trong khi xoá hình dạng giả.
ctx.rect(0, 0, width, emHeight);
ctx.clip();
Kết hợp tất cả lại với nhau và tối ưu hoá trong quá trình thực hiện – nếu bóng không có hiệu ứng làm mờ, bạn có thể sử dụng fillText để tạo hiệu ứng tương tự, giúp bạn không phải thiết lập mặt nạ cắt:
var width = ctx.measureText(text).width;
var style = shadowStyles[text];
// add a background to the current effect
ctx.fillStyle = style.background;
ctx.fillRect(0, offsetY, ctx.canvas.width, textHeight - 1)
// parse text-shadows from css
var shadows = parseShadow(style.shadow);
// loop through the shadow collection
var n = shadows.length; while(n--) {
var shadow = shadows[n];
var totalWidth = width + shadow.blur * 2;
ctx.save();
ctx.beginPath();
ctx.rect(offsetX - shadow.blur, offsetY, offsetX + totalWidth, textHeight);
ctx.clip();
if (shadow.blur) { // just run shadow (clip text)
ctx.shadowColor = shadow.color;
ctx.shadowOffsetX = shadow.x + totalWidth;
ctx.shadowOffsetY = shadow.y;
ctx.shadowBlur = shadow.blur;
ctx.fillText(text, -totalWidth + offsetX, offsetY + metrics.top);
} else { // just run pseudo-shadow
ctx.fillStyle = shadow.color;
ctx.fillText(text, offsetX + (shadow.x||0), offsetY - (shadow.y||0) + metrics.top);
}
ctx.restore();
}
// drawing the text in the foreground
if (style.color) {
ctx.fillStyle = style.color;
ctx.fillText(text, offsetX, offsetY + metrics.top);
}
// jump to next em-line
ctx.translate(0, textHeight);
Vì bạn sẽ không muốn nhập tất cả các lệnh <canvas>
này theo cách thủ công, nên tôi đã đưa một trình phân tích cú pháp bóng văn bản đơn giản vào nguồn minh hoạ; bằng cách này, bạn có thể cung cấp cho trình phân tích cú pháp này các lệnh CSS và yêu cầu trình phân tích cú pháp này tạo các lệnh <canvas>
.
Giờ đây, các phần tử <canvas>
của chúng ta có thể liên kết với nhiều kiểu.
Bạn có thể sử dụng các hiệu ứng bóng tương tự trên bất kỳ đối tượng vectơ nào, từ WebFonts đến các hình dạng phức tạp được nhập từ SVG, đến các hình dạng vectơ tạo sinh, v.v.!

Điểm chèn quảng cáo (tan trên pixel-pushing)
Khi viết phần này của bài viết, ví dụ về Kính lúp 3D đã khiến tôi tò mò. Bạn có thể tạo hiệu ứng màn hình phim 3D bằng <canvas>
và hai hình ảnh được chụp từ các góc độ hơi khác nhau không? Rõ ràng là không quá khó. Hạt nhân sau đây kết hợp kênh màu đỏ của hình ảnh đầu tiên (data) với kênh màu xanh lục lam của hình ảnh thứ hai (data2):
data[i] = data[i] * 255 / 0xFF;
data[i+1] = 255 * data2[i+1] / 0xFF;
data[i+2] = 255 * data2[i+2] / 0xFF;
Giờ đây, người dùng chỉ cần dùng băng keo dán hai chiếc iPhone lên trán, đồng thời nhấp vào nút "quay video" là có thể tự tạo phim 3D bằng HTML5. Có ai tình nguyện không?

Hiệu ứng chuỗi neon-rainbow, zebra-reflection
Việc tạo chuỗi nhiều hiệu ứng trong <canvas>
có thể đơn giản, nhưng bạn cần có kiến thức cơ bản về globalCompositeOperation (GCO). Để so sánh các thao tác với GIMP (hoặc Photoshop): có 12 GCO trong <canvas>
darker (tối hơn) và lighter (sáng hơn) có thể được coi là chế độ kết hợp lớp; 10 thao tác khác được áp dụng cho các lớp dưới dạng mặt nạ alpha (một lớp sẽ xoá các pixel của lớp khác). globalCompositeOperation liên kết các "lớp" (hoặc trong trường hợp của chúng ta là các chuỗi mã) với nhau, kết hợp các lớp đó theo những cách mới mẻ và thú vị:

Biểu đồ globalCompositeOperation cho thấy các chế độ GCO đang hoạt động; biểu đồ này sử dụng một phần lớn phổ màu và nhiều cấp độ độ trong suốt alpha để xem chi tiết những gì cần mong đợi. Bạn nên xem tài liệu tham khảo globalCompositeOperation của Mozilla để biết nội dung mô tả dạng văn bản. Để nghiên cứu thêm, bạn có thể tìm hiểu cách hoạt động của toán tử này trong bài viết Kết hợp hình ảnh kỹ thuật số của Porter Duff.
Chế độ yêu thích của tôi là globalCompositeOperation="lighter". Lighter kết hợp các pixel được thêm vào tương tự như cách ánh sáng kết hợp; khi ánh sáng đỏ, xanh lục và trắng ở cường độ đầy đủ, chúng ta sẽ thấy ánh sáng trắng. Đây là một tính năng thú vị để thử nghiệm, đặc biệt là khi <canvas>
được đặt thành globalAlpha thấp; cho phép kiểm soát tốt hơn và các cạnh mượt mà hơn. Lighter đã được sử dụng cho nhiều mục đích, gần đây nhất là một trình tạo hình nền máy tính để bàn HTML5 có tại http://weavesilk.com/.
Một trong các bản minh hoạ của tôi, Breathing Galaxies (JS1k), cũng sử dụng chế độ sáng hơn – vẽ các mẫu từ hai ví dụ này, bạn sẽ bắt đầu thấy hiệu ứng của chế độ này.
Xử lý trình duyệt globalCompositeOperation.
Hiệu ứng nhấp nháy Neon-Rainbow
Trong bản minh hoạ sau, chúng ta sẽ tạo hiệu ứng giống như Photoshop với ánh sáng cầu vồng neon có đường viền bị giật bằng cách nối các hiệu ứng với nhau bằng globalCompositeOperation (nguồn vào, sáng hơn và tối hơn).
Bản minh hoạ này là phần tiếp theo của bản minh hoạ "Hiệu ứng đổ bóng văn bản trong <canvas>
", sử dụng cùng một chiến lược để tách bóng khỏi văn bản (xem phần trước):

function neonLightEffect() {
var text = "alert('"+String.fromCharCode(0x2665)+"')";
var font = "120px Futura, Helvetica, sans-serif";
var jitter = 25; // the distance of the maximum jitter
var offsetX = 30;
var offsetY = 70;
var blur = getBlurValue(100);
// save state
ctx.save();
ctx.font = font;
// calculate width + height of text-block
var metrics = getMetrics(text, font);
// create clipping mask around text-effect
ctx.rect(offsetX - blur/2, offsetY - blur/2,
offsetX + metrics.width + blur, metrics.height + blur);
ctx.clip();
// create shadow-blur to mask rainbow onto (since shadowColor doesn't accept gradients)
ctx.save();
ctx.fillStyle = "#fff";
ctx.shadowColor = "rgba(0,0,0,1)";
ctx.shadowOffsetX = metrics.width + blur;
ctx.shadowOffsetY = 0;
ctx.shadowBlur = blur;
ctx.fillText(text, -metrics.width + offsetX - blur, offsetY + metrics.top);
ctx.restore();
// create the rainbow linear-gradient
var gradient = ctx.createLinearGradient(0, 0, metrics.width, 0);
gradient.addColorStop(0, "rgba(255, 0, 0, 1)");
gradient.addColorStop(0.15, "rgba(255, 255, 0, 1)");
gradient.addColorStop(0.3, "rgba(0, 255, 0, 1)");
gradient.addColorStop(0.5, "rgba(0, 255, 255, 1)");
gradient.addColorStop(0.65, "rgba(0, 0, 255, 1)");
gradient.addColorStop(0.8, "rgba(255, 0, 255, 1)");
gradient.addColorStop(1, "rgba(255, 0, 0, 1)");
// change composite so source is applied within the shadow-blur
ctx.globalCompositeOperation = "source-atop";
// apply gradient to shadow-blur
ctx.fillStyle = gradient;
ctx.fillRect(offsetX - jitter/2, offsetY,
metrics.width + offsetX, metrics.height + offsetY);
// change composite to mix as light
ctx.globalCompositeOperation = "lighter";
// multiply the layer
ctx.globalAlpha = 0.7
ctx.drawImage(ctx.canvas, 0, 0);
ctx.drawImage(ctx.canvas, 0, 0);
ctx.globalAlpha = 1
// draw white-text ontop of glow
ctx.fillStyle = "rgba(255,255,255,0.95)";
ctx.fillText(text, offsetX, offsetY + metrics.top);
// created jittered stroke
ctx.lineWidth = 0.80;
ctx.strokeStyle = "rgba(255,255,255,0.25)";
var i = 10; while(i--) {
var left = jitter / 2 - Math.random() * jitter;
var top = jitter / 2 - Math.random() * jitter;
ctx.strokeText(text, left + offsetX, top + offsetY + metrics.top);
}
ctx.strokeStyle = "rgba(0,0,0,0.20)";
ctx.strokeText(text, offsetX, offsetY + metrics.top);
ctx.restore();
};
Hiệu ứng phản chiếu Zebra
Hiệu ứng phản chiếu Zebra được lấy cảm hứng từ tài nguyên tuyệt vời của WebDesignerWall về cách làm cho trang của bạn trở nên hấp dẫn hơn bằng CSS. Điều này giúp ý tưởng này đi xa hơn một chút, tạo ra một "hiệu ứng phản chiếu" cho văn bản – chẳng hạn như nội dung bạn có thể thấy trong iTunes. Hiệu ứng này kết hợp fillColor (màu trắng), createPattern (zebra.png) và linearGradient (shine); điều này minh hoạ khả năng áp dụng nhiều loại màu tô cho mỗi đối tượng vectơ:

function sleekZebraEffect() {
// inspired by - http://www.webdesignerwall.com/demo/css-gradient-text/
var text = "Sleek Zebra...";
var font = "100px Futura, Helvetica, sans-serif";
// save state
ctx.save();
ctx.font = font;
// getMetrics calculates:
// width + height of text-block
// top + middle + bottom baseline
var metrics = getMetrics(text, font);
var offsetRefectionY = -20;
var offsetY = 70;
var offsetX = 60;
// throwing a linear-gradient in to shine up the text
var gradient = ctx.createLinearGradient(0, offsetY, 0, metrics.height + offsetY);
gradient.addColorStop(0.1, '#000');
gradient.addColorStop(0.35, '#fff');
gradient.addColorStop(0.65, '#fff');
gradient.addColorStop(1.0, '#000');
ctx.fillStyle = gradient
ctx.fillText(text, offsetX, offsetY + metrics.top);
// draw reflected text
ctx.save();
ctx.globalCompositeOperation = "source-over";
ctx.translate(0, metrics.height + offsetRefectionY)
ctx.scale(1, -1);
ctx.font = font;
ctx.fillStyle = "#fff";
ctx.fillText(text, offsetX, -metrics.height - offsetY + metrics.top);
ctx.scale(1, -1);
// cut the gradient out of the reflected text
ctx.globalCompositeOperation = "destination-out";
var gradient = ctx.createLinearGradient(0, offsetY, 0, metrics.height + offsetY);
gradient.addColorStop(0.0, 'rgba(0,0,0,0.65)');
gradient.addColorStop(1.0, '#000');
ctx.fillStyle = gradient;
ctx.fillRect(offsetX, offsetY, metrics.width, metrics.height);
// restore back to original transform state
ctx.restore();
// using source-atop to allow the transparent .png to show through to the gradient
ctx.globalCompositeOperation = "source-atop";
// creating pattern from <image> sourced.
ctx.fillStyle = ctx.createPattern(image, 'repeat');
// fill the height of two em-boxes, to encompass both normal and reflected state
ctx.fillRect(offsetX, offsetY, metrics.width, metrics.height * 2);
ctx.restore();
};
Bóng trong/ngoài trong Canvas
Thông số kỹ thuật <canvas>
không đề cập đến chủ đề bóng "bên trong"
so với bóng "bên ngoài". Trên thực tế, ở lần đầu xuất hiện, bạn có thể thấy bóng "bên trong" không được hỗ trợ. Không phải vậy.
Việc bật tính năng này hơi phức tạp một chút ;) Như đề xuất trong một bài đăng gần đây của F1LT3R, bạn có thể tạo bóng bên trong bằng cách sử dụng các thuộc tính riêng biệt của quy tắc xoắn theo chiều kim đồng hồ so với quy tắc xoắn ngược chiều kim đồng hồ. Để làm việc này, bạn tạo một "inner-shadow" (bóng bên trong) bằng cách vẽ hình chữ nhật vùng chứa, sau đó sử dụng các quy tắc xoắn ngược để vẽ một hình cắt – tạo hình ngược của hình đó.
Ví dụ sau cho phép tạo kiểu cho bóng đổ bên trong và fillStyle bằng màu+độ dốc+mẫu cùng một lúc. Bạn có thể chỉ định từng lượt xoay mẫu; hãy lưu ý rằng các sọc của ngựa vằn hiện vuông góc với nhau. Một mặt nạ cắt có kích thước của hộp giới hạn được sử dụng để loại bỏ nhu cầu về một vùng chứa siêu lớn nhằm bao bọc hình dạng cắt – cải thiện tốc độ bằng cách ngăn chặn việc xử lý các phần không cần thiết của bóng.

function innerShadow() {
function drawShape() { // draw anti-clockwise
ctx.arc(0, 0, 100, 0, Math.PI * 2, true); // Outer circle
ctx.moveTo(70, 0);
ctx.arc(0, 0, 70, 0, Math.PI, false); // Mouth
ctx.moveTo(-20, -20);
ctx.arc(30, -30, 10, 0, Math.PI * 2, false); // Left eye
ctx.moveTo(140, 70);
ctx.arc(-20, -30, 10, 0, Math.PI * 2, false); // Right eye
};
var width = 200;
var offset = width + 50;
var innerColor = "rgba(0,0,0,1)";
var outerColor = "rgba(0,0,0,1)";
ctx.translate(150, 170);
// apply inner-shadow
ctx.save();
ctx.fillStyle = "#000";
ctx.shadowColor = innerColor;
ctx.shadowBlur = getBlurValue(120);
ctx.shadowOffsetX = -15;
ctx.shadowOffsetY = 15;
// create clipping path (around blur + shape, preventing outer-rect blurring)
ctx.beginPath();
ctx.rect(-offset/2, -offset/2, offset, offset);
ctx.clip();
// apply inner-shadow (w/ clockwise vs. anti-clockwise cutout)
ctx.beginPath();
ctx.rect(-offset/2, -offset/2, offset, offset);
drawShape();
ctx.fill();
ctx.restore();
// cutout temporary rectangle used to create inner-shadow
ctx.globalCompositeOperation = "destination-out";
ctx.fill();
// prepare vector paths
ctx.beginPath();
drawShape();
// apply fill-gradient to inner-shadow
ctx.save();
ctx.globalCompositeOperation = "source-in";
var gradient = ctx.createLinearGradient(-offset/2, 0, offset/2, 0);
gradient.addColorStop(0.3, '#ff0');
gradient.addColorStop(0.7, '#f00');
ctx.fillStyle = gradient;
ctx.fill();
// apply fill-pattern to inner-shadow
ctx.globalCompositeOperation = "source-atop";
ctx.globalAlpha = 1;
ctx.rotate(0.9);
ctx.fillStyle = ctx.createPattern(image, 'repeat');
ctx.fill();
ctx.restore();
// apply fill-gradient
ctx.save();
ctx.globalCompositeOperation = "destination-over";
var gradient = ctx.createLinearGradient(-offset/2, -offset/2, offset/2, offset/2);
gradient.addColorStop(0.1, '#f00');
gradient.addColorStop(0.5, 'rgba(255,255,0,1)');
gradient.addColorStop(1.0, '#00f');
ctx.fillStyle = gradient
ctx.fill();
// apply fill-pattern
ctx.globalCompositeOperation = "source-atop";
ctx.globalAlpha = 0.2;
ctx.rotate(-0.4);
ctx.fillStyle = ctx.createPattern(image, 'repeat');
ctx.fill();
ctx.restore();
// apply outer-shadow (color-only without temporary layer)
ctx.globalCompositeOperation = "destination-over";
ctx.shadowColor = outerColor;
ctx.shadowBlur = 40;
ctx.shadowOffsetX = 15;
ctx.shadowOffsetY = 10;
ctx.fillStyle = "#fff";
ctx.fill();
};
Bạn có thể thấy từ các ví dụ này, bằng cách sử dụng globalCompositeOperation, chúng ta có thể tạo hiệu ứng chuỗi với nhau, tạo ra các hiệu ứng phức tạp hơn (sử dụng tính năng che và kết hợp). Màn hình là nơi bạn có thể thỏa sức sáng tạo ;)
Spaceage – hiệu ứng tạo sinh
Trong <canvas>
, chuyển từ ký tự unicode 0x2708:

…thành ví dụ được tô bóng sau:

…có thể được thực hiện bằng nhiều lệnh gọi đến ctx.strokeText()
với lineWidth mỏng (0,25), đồng thời giảm dần độ lệch x và alpha; tạo cảm giác chuyển động cho các phần tử vectơ.
Bằng cách liên kết vị trí XY của các phần tử với sóng sin/cosin và luân phiên các màu bằng thuộc tính HSL, chúng ta có thể tạo ra các hiệu ứng thú vị hơn, chẳng hạn như ví dụ "nguy hiểm sinh học" này:

HSL: Hue, Saturation, Lightness (Tông màu, Độ bão hoà, Độ sáng) (1978)
HSL là một định dạng mới được hỗ trợ trong thông số kỹ thuật CSS3. HEX được thiết kế cho máy tính, còn HSL được thiết kế để con người có thể đọc được.
Minh hoạ sự dễ dàng của HSL; để luân phiên qua quang phổ màu, chúng ta chỉ cần tăng "hue" (tông màu) từ 360; tông màu được liên kết với quang phổ theo hình trụ. Độ sáng kiểm soát độ đậm/nhạt của màu; 0% cho biết một pixel màu đen, trong khi 100% cho biết một pixel màu trắng. Độ bão hoà kiểm soát độ sáng hoặc độ sống động của màu; màu xám được tạo với độ bão hoà là 0% và màu sống động được tạo bằng giá trị 100%.

Vì HSL là một tiêu chuẩn mới, nên bạn có thể tiếp tục hỗ trợ các trình duyệt cũ thông qua việc chuyển đổi không gian màu. Mã sau đây chấp nhận một đối tượng HSL { H: 360, S: 100, L: 100} và xuất một đối tượng RGB { R: 255, G: 255, B: 255 }. Từ đó, bạn có thể sử dụng các giá trị đó để tạo chuỗi rgb hoặc rgba. Để biết thêm thông tin chi tiết, hãy xem bài viết chuyên sâu của Wikipedia về HSL.
// HSL (1978) = H: Hue / S: Saturation / L: Lightness
HSL_RGB = function (o) { // { H: 0-360, S: 0-100, L: 0-100 }
var H = o.H / 360,
S = o.S / 100,
L = o.L / 100,
R, G, B, _1, _2;
function Hue_2_RGB(v1, v2, vH) {
if (vH < 0) vH += 1;
if (vH > 1) vH -= 1;
if ((6 * vH) < 1) return v1 + (v2 - v1) * 6 * vH;
if ((2 * vH) < 1) return v2;
if ((3 * vH) < 2) return v1 + (v2 - v1) * ((2 / 3) - vH) * 6;
return v1;
}
if (S == 0) { // HSL from 0 to 1
R = L * 255;
G = L * 255;
B = L * 255;
} else {
if (L < 0.5) {
_2 = L * (1 + S);
} else {
_2 = (L + S) - (S * L);
}
_1 = 2 * L - _2;
R = 255 * Hue_2_RGB(_1, _2, H + (1 / 3));
G = 255 * Hue_2_RGB(_1, _2, H);
B = 255 * Hue_2_RGB(_1, _2, H - (1 / 3));
}
return {
R: R,
G: G,
B: B
};
};
Tạo ảnh động bằng requestAnimationFrame
Trước đây, để tạo ảnh động trong JavaScript, bạn có hai lựa chọn: setTimeout
và setInterval
.
window.requestAnimationFrame
là tiêu chuẩn mới để thay thế cả hai; tiết kiệm điện năng trên toàn cầu (và một vài nhịp tim của máy tính) bằng cách cho phép trình duyệt điều chỉnh ảnh động dựa trên tài nguyên có sẵn.
Một số tính năng quan trọng bao gồm:
- Khi người dùng thoát khỏi khung, ảnh động có thể bị chậm lại hoặc dừng hoàn toàn để tránh sử dụng tài nguyên không cần thiết.
- Tốc độ khung hình có giới hạn là 60 khung hình/giây. Lý do là tốc độ này cao hơn nhiều so với mức mà con người có thể nhận thấy (hầu hết mọi người ở tốc độ 30 khung hình/giây sẽ thấy ảnh động "mượt mà").
Tại thời điểm viết bài, bạn cần có tiền tố cụ thể của nhà cung cấp để sử dụng requestAnimationFrame
.
Paul Irish đã tạo một lớp đệm có hỗ trợ đa nhà cung cấp, trong requestAnimationFrame để tạo ảnh động thông minh:
// shim layer with setTimeout fallback
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(/* function */ callback, /* DOMElement */ element){
window.setTimeout(callback, 1000 / 60);
};
})();
Mở rộng một chút, bạn có thể kết hợp điều này với một tính năng lấp đầy nhiều lần như requestAnimationFrame.js (có một vài tính năng cần được xử lý) để hỗ trợ các trình duyệt cũ hơn ở mức độ cao hơn, đồng thời chuyển sang tiêu chuẩn mới này.
(function animate() {
var i = 50;
while(i--) {
if (n > endpos) return;
n += definition;
ctx.globalAlpha = (0.5 - (n + startpos) / endpos) * alpha;
if (doColorCycle) {
hue = n + color;
ctx.strokeStyle = "hsl(" + (hue % 360) + ",99%,50%)"; // iterate hue
}
var x = cos(n / cosdiv) * n * cosmult; // cosine
var y = sin(n / sindiv) * n * sinmult; // sin
ctx.strokeText(text, x + xoffset, y + yoffset); // draw rainbow text
}
timeout = window.requestAnimationFrame(animate, 0);
})();



Mã nguồn
Với sự hỗ trợ của các nhà cung cấp trình duyệt, không có gì phải nghi ngờ về tương lai của <canvas>
. Bạn có thể chuyển <canvas>
sang các tệp thực thi iPhone/Android/Máy tính bằng PhoneGap hoặc