Hiệu ứng kiểu chữ trong canvas

Lý lịch trích ngang của tôi

<canvas> biết đến tôi vào năm 2006 khi Firefox phiên bản 2.0 được phát hành. Một bài viết về 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 cầu (2007). Điều này khiến tôi đắm chìm vào thế giới màu sắc và những yếu tố đồ hoạ nguyên thuỷ; truyền cảm hứng cho việc tạo ra Sketchpad (2007-2008) với nỗ lực tập hợp 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 sự ra đời của công ty khởi nghiệp Mugtug với người bạn thân thiết của tôi là Charles Pritchard. Chúng tôi đang phát triển Darkroom trong HTML5 <canvas>. Phòng tối là một ứng dụng chia sẻ ảnh không phá huỷ, 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

Ảnh biểu ngữ trên canvas.

<canvas> cho phép lập trình viên JavaScript toàn quyền kiểm soát màu sắc, vectơpixel trên màn hình của họ – thành phần hình ảnh của màn hình.

Các ví dụ sau đây đề cập đến một vùng chưa được chú ý nhiều trong <canvas>; đó là việc tạo hiệu ứng văn bản. Bạn có thể tạo ra nhiều hiệu ứng văn bản trong <canvas> đa dạng như bạn có thể tưởng tượng. Các bản minh hoạ này bao gồm một phần phụ về những gì có thể thực hiện. Mặc dù chúng tôi đang xử lý "văn bản" trong bài viết này, nhưng bạn có thể áp dụng các phương thức này 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:

Bóng văn bản trong <canvas>.
Hiệu ứng văn bản giống CSS trong <canvas> trong việc 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 ngựa vằn – các hiệu ứng chuỗi.
Hiệu ứng văn bản giống như Photoshop trong <canvas> ví dụ về cách sử dụng globalComponentOperation, createLinearColorient, createPattern.
Bóng bên trong và bên ngoài trong <canvas>
Tiết lộ một tính năng ít được biết đến; sử dụng tính năng uốn lượn theo chiều kim đồng hồ so với ngược chiều kim đồng hồ để tạo ra nghịch đảo của bóng đổ (vùng bóng bên trong).
Spaceage – hiệu ứng tạo sinh.
Hiệu ứng văn bản dựa trên AI 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 cho thông số kỹ thuật CSS3 (cùng với đường viền bán kính, chuyển màu web 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 cần nhận ra sự khác biệt giữa bóng CSS và bóng <canvas>, cụ thể là:

CSS sử dụng 2 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 nhấn vào 4 thuộc tính sau:

ctx.shadowColor = "red" // chuỗi
Màu của bóng đổ; RGB, RGBA, HSL, FORM và các dữ liệu đầu vào khác hợp lệ.
ctx.shadowOffsetX = 0; // số nguyên
Khoảng cách theo chiều ngang của bóng, tương quan 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ờ đối với bóng đổ, giá trị càng lớn, độ 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. Việc tìm kiếm qua các hình ảnh trên Google về "đổ bóng văn bản css" đã dẫn đến một số bản minh hoạ tuyệt vời để chúng ta mô phỏng; Line25, sterscopicShadow 3D.

Đồ họa CSS 3D

Hiệu ứng 3D lập thể (xem Hình ảnh định dạng để biết thêm thông tin) là một ví dụ về dòng mã đơn giản, sẽ rất hữu ích. Với dòng CSS sau đây, chúng tôi có thể tạo ra ảo ảnh về chiều sâu khi xem bằng kính 3D màu đỏ/lục lam (loại kính họ cung cấp cho bạn khi xem 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>:

  1. Không có bóng đổ (giá trị thứ ba) nên không có lý do gì để thực sự chạy bóng đổ, 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>
  1. EM không được hỗ trợ trong <canvas>, vì vậy sẽ phải chuyển đổi sang PX. Chúng ta có thể tìm thấy tỷ lệ chuyển đổi để chuyển đổi giữa PT, PC, EM, EX, PX và tương tự 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; hoặc ví dụ: để thu thập lượt chuyển đổi EM -> PX, chúng ta sẽ đo lường phần tử DOM với "chiều cao: 1em", kết quả chênh lệch EM sẽ là bao nhiêu 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 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, chúng ta sẽ gặp phải một sự cố; 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 chính nó. Điều này dẫn đến hiện tượng nhân alpha và cuối cùng là các cạnh lởm chởm.

Đồ họa neon

Tôi đã thử chạy ctx.fillStyle = "rgba(0,0,0,0)" hoặc "transparent" để ẩn văn bản, trong khi hiện bóng đổ... tuy nhiên, cách này không có hiệu quả; vì bóng là phần nhân của alpha fillStyle, nên bóng không bao giờ có thể mờ hơn so với 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 đổ khỏi văn bản, tách riêng chúng (để chúng không chồng chéo) và do đó ẩn văn bản ra khỏi 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 khối văn bản

Để dọn dẹp phần này, chúng ta có thể ngăn fillText được vẽ ở vị trí đầu tiên (trong khi cho phép vẽ bóng) bằng cách thêm đường dẫn cắt. Để tạo đường dẫn cắt xung quanh văn bản, chúng ta cần biết chiều cao của văn bản (trước đây gọi là "em-height" là chiều cao của chữ "M" trên máy in) và chiều rộng của văn bản. Chúng ta có thể có chiều rộng bằng cách sử dụng ctx.measureText().width. Tuy nhiên, ctx.measureText().height không tồn tại.

May mắn thay, thông qua kỹ thuật tấn công CSS (xem Chỉ số kiểu chữ để biết thêm các cách khắc phục các cách triển khai cũ của <canvas> bằng cách sử dụng 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 offsetHeight của <span> với cùng các 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 để sử 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();

Liên kết tất cả với nhau và tối ưu hoá trong quá trình di chuyển – nếu bóng không bị mờ, chúng ta có thể sử dụng fillText với hiệu ứng tương tự, giúp chúng ta không phải thiết lập mặt nạ cắt (clipping mask):

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 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 đã cung cấp một trình phân tích cú pháp bóng văn bản đơn giản trong nguồn minh hoạ. Bằng cách này, bạn có thể cấp dữ liệu cho các lệnh CSS và yêu cầu công cụ này tạo các lệnh <canvas>. Giờ đây, các phần tử <canvas> của chúng ta có toàn bộ nhiều kiểu có thể liên kết. Bạn có thể sử dụng các hiệu ứng bóng này 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 hình dạng vectơ tạo sinh, v.v.!

Bóng văn bản trong hiệu ứng canvas

gián đoạn (tiếp xúc khi đẩy điểm ảnh)

Khi viết phần này của bài viết, ví dụ về hình nổi khiến tôi tò mò. Sẽ khó đến mức nào khi tạo hiệu ứng màn hình phim 3D bằng cách dùng <canvas> và hai bức ảnh chụp từ các góc nhìn hơi khác nhau? Tôi thấy việc này cũng không quá khó khăn. Nhân sau đây kết hợp kênh màu đỏ của hình ảnh thứ nhất (dữ liệu) với kênh màu 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, ai đó chỉ cần dán hai chiếc iPhone lên trán, nhấp vào "quay video" cùng lúc và chúng ta có thể tạo phim 3D của riêng mình bằng HTML5. Có tình nguyện viên nào không?

kính 3d

Hiệu ứng cầu vồng neon, phản chiếu ngựa vằn — chuỗi

Việc xâu 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ề GlobalComOperation (GCO). Để so sánh các thao tác với GIMP (hoặc Photoshop): có 12 GCO trong <canvas> tối hơnsáng hơn có thể được coi là chế độ kết hợp lớp; 10 thao tác còn lại đượ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 điểm ảnh của lớp còn lại). GlobalComponentOperation liên kết các "lớp" (hoặc trong trường hợp của chúng tôi là chuỗi mã) với nhau, kết hợp chúng theo những cách mới và thú vị:

Đồ hoạ hiệu ứng theo chuỗi

Biểu đồ toàn cục composite cho thấy các chế độ GCO đang hoạt động; biểu đồ này sử dụng phần lớn các phổ màu và nhiều mức độ trong suốt alpha để biết chi tiết những điều cần biết. Bạn nên xem tài liệu tham khảo về toàn bộ GlobalComOperation của Mozilla để biết những 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 thao tác này trong bài viết Kết hợp hình ảnh kỹ thuật số của Porter Duff.

Chế độ ưa thích của tôi là globalComponentOperation="lighter". Nhẹ hơn sẽ kết hợp các pixel được thêm vào tương tự như cách kết hợp ánh sáng; khi ánh sáng đỏ, xanh lục và trắng ở cường độ tối đa, chúng ta sẽ thấy ánh sáng trắng. Đây là một tính năng thú vị để bạn khám phá, đặc biệt là khi <canvas> được đặt thành globalAlpha thấp; cho phép điều khiển chi tiết hơn và các cạnh mượt mà hơn. Nhẹ hơn đã được sử dụng nhiều, mục yêu thích gần đây của tôi là trình tạo nền HTML5 cho máy tính để bàn được tìm thấy tại http://weavesilk.com/. Một trong những bản minh hoạ của tôi, Thiên hà thở (JS1k), cũng sử dụng chế độ sáng hơn – vẽ các mẫu từ hai ví dụ này để bạn bắt đầu thấy hiệu ứng mà chế độ này mang lại.

Xử lý trình duyệt globalGlobalComOperation.

Hiệu ứng dao động cầu vồng neon

Trong bản minh hoạ sau, chúng ta sẽ tạo ra một hiệu ứng neon-cầu vồng giống như Photoshop với một đường viền bị xáo trộn, bằng cách xâu chuỗi các hiệu ứng với nhau bằng cách sử dụng globalsynOperation (ở chế độ nguồn, sáng hơn và tối hơn). Bản minh hoạ này là tiến trình của bản minh hoạ "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):

Dao động cầu vồng
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 hình ảnh ngựa vằn

Hiệu ứng Phản chiếu Zebra được lấy cảm hứng từ tài nguyên hữu ích của WebDesignerWall về cách giúp trang của bạn trở nên sinh động hơn bằng CSS. Việc này sẽ đưa ý tưởng xa hơn một chút, tạo ra "sự phản chiếu" cho văn bản, chẳng hạn như những gì 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 (sáng); minh hoạ khả năng áp dụng nhiều kiểu màu nền cho từng đối tượng vectơ:

Hiệu ứng ngựa vằn
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 bên trong/bên ngoài trong Canvas

Thông số kỹ thuật của <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ế, trong lần xuất hiện đầu tiên, có thể bạn sẽ thấy bóng "bên trong" không được hỗ trợ. Không phải lúc này. Sẽ phức tạp hơn một chút để bật ;) Như đã đề xuất trong một bài đăng gần đây từ 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 cuộn theo chiều kim đồng hồ so với quy tắc cuộn ngược chiều kim đồng hồ. Để thực hiện việc này, bạn tạo "đổ 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 cuộn dây đối diện, vẽ một hình cắt – tạo ra nghịch đảo của hình dạng.

Ví dụ sau đây cho phép hiệu ứng bóng đổ bên trong và fillStyle được cách điệu bằng color+gradient+pattern cùng lúc. Bạn có thể chỉ định riêng từng chế độ xoay mẫu; chú ý các sọc ngựa vằn hiện vuông góc với nhau. Dùng mặt nạ cắt kích thước của hộp giới hạn để loại bỏ nhu cầu phải dùng một vùng chứa siêu lớn để bao quanh hình dạng vết cắt – cải thiện tốc độ bằng cách ngăn xử lý các phần không cần thiết của bóng đổ.

Bóng bên trong/bên ngoài
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();
};

Từ những ví dụ này, bạn có thể thấy rằng bằng cách sử dụng globalComponentOperation, chúng ta có thể kết hợp các hiệu ứng chuỗi với nhau, tạo ra hiệu ứng phức tạp hơn (sử dụng việc tạo lớp phủ và kết hợp). Màn hình chính là nút thắt của bạn ;)

Vũ trụ – các hiệu ứng tạo sinh

Trong <canvas>, lấy từ ký tự unicode 0x2708:

Unicode gfaphic

...đối với ví dụ được tô bóng này:

Ví dụ minh hoạ

...có thể đạt được bằng nhiều lệnh gọi đến ctx.strokeText() với đường kẻ mỏng (0,25), trong khi giảm dần độ lệch x và alpha; mang lại cho các phần tử vectơ của chúng ta cảm giác chuyển động.

Bằng cách ánh xạ các phần tử XY với sóng sin/cos và chuyển đổi qua 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ụ về "biohazard":

Hiệu ứng đạp xe HSL

HSL: Màu sắc, Độ rực màu, Độ sáng (1978)

HSL là một định dạng mới được hỗ trợ trong thông số kỹ thuật CSS3. Trong đó, HSL đượ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; để xoay vòng phổ màu, chúng tôi chỉ cần tăng "màu sắc" từ 360; sắc độ được ánh xạ tới phổ theo kiểu hình trụ. Độ sáng kiểm soát mức độ tối/sáng của màu; 0% biểu thị điểm ảnh màu đen, trong khi 100% biểu thị điểm ảnh màu trắng. Độ rực màu 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 rực rỡ được tạo bằng giá trị 100%.

Đồ hoạ HSL

Vì HSL là một tiêu chuẩn mới đây, nên bạn nên tiếp tục hỗ trợ các trình duyệt cũ (có thể thực hiện thông qua việc chuyển đổi hệ màu). Mã sau đây chấp nhận đối tượng HSL { H: 360, S: 100, L: 100} và xuất ra 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 chuyên sâu, hãy xem bài viết chi tiết trên 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: setTimeoutsetInterval.

window.requestAnimationFrame là tiêu chuẩn mới tại đây để thay thế cả hai; giúp tiết kiệm điện (và máy tính của bạn một vài nhịp) 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 tồn tại khung hình, ảnh động có thể làm chậm hoặc dừng hoàn toàn để ngăn việc sử dụng tài nguyên không cần thiết.
  • Có giới hạn về tốc độ khung hình ở 60 khung hình/giây. Lý do là nó vượt quá mức mà mọi người có thể nhận thấy (hầu hết con người với tốc độ 30 khung hình/giây sẽ thấy ảnh động là "linh hoạt").

Tại thời điểm viết, các tiền tố cụ thể của vender bắt buộc phải sử dụng requestAnimationFrame. Paul Ireland đã tạo một lớp đệm có tính năng hỗ trợ giao diện chéo, 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);
        };
})();

Hơn nữa, việc tham vọng hơn nữa có thể ràng buộc điều này với một poly-fill như requestAnimationFrame.js (có một vài tính năng cần được nghiên cứu) sẽ hỗ trợ các trình duyệt cũ hơn nữa, đồ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);
})();
Đồ hoạ làm mờ ghi chú
Đồ hoạ ảnh động
Đồ họa ma trận

Mã nguồn

Với sự hỗ trợ từ nhiều hình ảnh khác nhau của nhà cung cấp trình duyệt, không có câu hỏi nào về việc tương lai của <canvas> có thể được chuyển sang các tệp thực thi dành cho iPhone/Android/Máy tính bằng cách sử dụng PhoneGap, hoặc

Titan.