Efek tipografi di kanvas

Latar belakang saya

<canvas> mulai saya ketahui pada tahun 2006 saat Firefox v2.0 dirilis. Sebuah artikel di Ajaxian, yang menjelaskan matriks transformasi, menginspirasi saya untuk membuat aplikasi web <canvas> pertama saya; Color Sphere (2007). Hal ini membuat saya tenggelam dalam dunia warna dan primitif grafis, yang menginspirasi pembuatan Sketchpad (2007-2008) dalam upaya menyusun aplikasi "yang lebih baik daripada Paint" di browser. Eksperimen ini akhirnya mengarah pada pembuatan startup Mugtug bersama teman lama saya, Charles Pritchard. Kami sedang mengembangkan Darkroom di HTML5 <canvas>. Darkroom adalah aplikasi berbagi foto non-destruktif, yang menggabungkan kecanggihan filter berbasis piksel dengan tipografi dan gambar berbasis vektor.

Pengantar

Gambar banner Canvas.

<canvas> memberi programmer Javascript kontrol penuh atas warna, vektor, dan piksel di layar mereka - susunan visual monitor.

Contoh berikut membahas satu area di <canvas> yang belum mendapat banyak perhatian; membuat efek teks. Variasi efek teks yang dapat dibuat di <canvas> sangat luas seperti yang dapat Anda bayangkan - demo ini mencakup sub-bagian dari apa yang mungkin dilakukan. Meskipun kita membahas "teks" dalam artikel ini, metode ini dapat diterapkan ke objek vektor apa pun; membuat visual yang menarik dalam game, dan aplikasi lainnya:

Bayangan Teks di <canvas>.
Efek teks mirip CSS di <canvas> yang membuat clipping mask, menemukan metrik di <canvas>, dan menggunakan properti bayangan.
Neon-pelangi, refleksi-zebra - efek berantai.
Efek teks seperti Photoshop di <canvas> contoh penggunaan globalCompositeOperation, createLinearGradient, createPattern.
Bayangan dalam dan luar di <canvas>
Mengungkap fitur yang jarang diketahui; menggunakan penggulungan searah jarum jam vs. berlawanan arah jarum jam untuk membuat kebalikan dari drop-shadow (inner-shadow).
Spaceage - efek generatif.
Efek teks berbasis generatif
di <canvas> menggunakan perputaran warna hsl() dan window.requestAnimationFrame untuk menciptakan kesan gerakan.

Bayangan teks di kanvas

Salah satu tambahan favorit saya pada spesifikasi CSS3 (bersama dengan border-radius, web-gradients, dan lainnya) adalah kemampuan untuk membuat bayangan. Penting untuk memahami perbedaan antara bayangan CSS dan <canvas>, khususnya:

CSS menggunakan dua metode; box-shadow untuk elemen kotak, seperti div, span, dan sebagainya; serta text-shadow untuk konten teks.

<canvas> memiliki satu jenis bayangan; bayangan ini digunakan untuk semua objek vektor; ctx.moveTo, ctx.lineTo, ctx.bezierCurveTo, ctx.quadradicCurveTo, ctx.arc, ctx.rect, ctx.fillText, ctx.strokeText, dan sebagainya. Untuk membuat bayangan di <canvas>, manfaatkan empat properti berikut:

ctx.shadowColor = "red" // string
Warna bayangan; RGB, RGBA, HSL, HEX, dan input lainnya valid.
ctx.shadowOffsetX = 0; // integer
Jarak horizontal bayangan, dalam kaitannya dengan teks.
ctx.shadowOffsetY = 0; // integer
Jarak vertikal bayangan, dalam kaitannya dengan teks.
ctx.shadowBlur = 10; // integer
Efek blur pada bayangan, makin besar nilainya, makin besar blurnya.

Untuk memulai, mari kita lihat cara <canvas> dapat mengemulasi efek CSS. Menelusuri "css text-shadow" melalui Google Gambar menghasilkan beberapa demo bagus untuk kita tiru; Line25, dan Stereoscopic, serta Shadow 3D.

Grafis 3D CSS

Efek 3D stereoskopis (lihat Gambar anaglif untuk mengetahui informasi selengkapnya) adalah contoh baris kode sederhana yang digunakan dengan sangat baik. Dengan baris CSS berikut, kita dapat menciptakan ilusi kedalaman saat dilihat dengan kacamata 3D merah/cyan (jenis yang diberikan di bioskop 3D):

text-shadow: -0.06em 0 0 red, 0.06em 0 0 cyan;

Ada dua hal yang perlu diperhatikan saat mengonversi string ini ke <canvas>:

  1. Tidak ada shadow-blur (nilai ketiga), jadi tidak ada alasan untuk benar-benar menjalankan bayangan, karena fillText akan memberikan hasil yang sama:
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 tidak didukung di <canvas>, sehingga harus dikonversi ke PX. Kita dapat menemukan rasio konversi untuk mengonversi antara PT, PC, EM, EX, PX, dan sebagainya dengan membuat elemen dengan properti font yang sama di DOM, dan menetapkan lebar ke format yang akan diukur; misalnya, untuk menangkap konversi EM -> PX, kita akan mengukur elemen DOM dengan "height: 1em", offsetHeight yang dihasilkan akan menjadi jumlah PX dalam setiap 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>

Mencegah perkalian alfa

Dalam contoh yang lebih kompleks, seperti efek Neon yang ditemukan di Line25, properti shadowBlur harus digunakan untuk mengemulasi efek dengan benar. Karena efek Neon mengandalkan beberapa bayangan, kita mengalami masalah; di <canvas>, setiap objek vektor hanya dapat memiliki satu bayangan. Jadi, untuk menggambar beberapa bayangan, Anda harus menggambar beberapa versi teks di atasnya. Hal ini menghasilkan perkalian alfa, dan pada akhirnya tepi bergerigi.

Grafis neon

Saya mencoba menjalankan ctx.fillStyle = "rgba(0,0,0,0)" atau "transparent" untuk menyembunyikan teks, sambil menampilkan bayangan… namun, upaya ini sia-sia; karena bayangan adalah perkalian dari alfa fillStyle, bayangan tidak akan pernah bisa lebih buram daripada fillStyle.

Untungnya, ada cara untuk mengatasi hal ini. Kita dapat menggambar offset bayangan dari teks, sehingga keduanya tetap terpisah (tidak tumpang-tindih), dan dengan demikian menyembunyikan teks di sisi layar:

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);

Pemangkasan di sekitar blok teks

Untuk merapikannya, kita dapat mencegah fillText digambar sejak awal (sementara mengizinkan bayangan digambar) dengan menambahkan jalur pemangkasan. Untuk membuat jalur pemangkasan yang mengelilingi teks, kita perlu mengetahui tinggi teks (disebut "em-height", yang secara historis merupakan tinggi huruf "M" pada mesin cetak), dan lebar teks. Kita bisa mendapatkan lebar menggunakan ctx.measureText().width, tetapi ctx.measureText().height tidak ada.

Untungnya, melalui peretasan CSS (lihat Metrik Tipografi untuk mengetahui cara lain memperbaiki penerapan <canvas> yang lebih lama menggunakan pengukuran CSS), kita dapat menemukan tinggi teks dengan mengukur offsetHeight dari <span> dengan properti font yang sama:

var d = document.createElement("span");
d.font = "20px arial"
d.textContent = "Hello world!"
var emHeight = d.offsetHeight;

Dari sana, kita dapat membuat persegi panjang untuk digunakan sebagai jalur pemangkasan; menyertakan "bayangan" sekaligus menghapus bentuk tiruan.

ctx.rect(0, 0, width, emHeight);
ctx.clip();

Menggabungkan semuanya, dan mengoptimalkan seiring berjalannya waktu - jika bayangan tidak memiliki blur, fillText dapat digunakan untuk efek yang sama, sehingga kita tidak perlu menyiapkan 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);

Karena Anda tidak ingin memasukkan semua perintah <canvas> ini secara manual, saya telah menyertakan parser text-shadow sederhana dalam sumber demo; dengan cara ini, Anda dapat memasukkan perintah CSS dan membuatnya menghasilkan perintah <canvas>. Sekarang, elemen <canvas> kita memiliki berbagai gaya yang dapat dikaitkan. Efek bayangan yang sama ini dapat digunakan pada objek vektor apa pun, mulai dari WebFont hingga bentuk kompleks yang diimpor dari SVG, bentuk vektor generatif, dan sebagainya.

Bayangan teks dalam efek kanvas

Intermisi (singgungan pada pixel-pushing)

Saat menulis bagian artikel ini, contoh Stereoskopis membuat saya penasaran. Seberapa sulitkah membuat efek layar film 3D menggunakan <canvas> dan dua gambar yang diambil dari perspektif yang sedikit berbeda? Sepertinya tidak terlalu sulit. Kernel berikut menggabungkan saluran merah gambar pertama (data) dengan saluran cyan gambar kedua (data2):

data[i] = data[i] * 255 / 0xFF;
data[i+1] = 255 * data2[i+1] / 0xFF;
data[i+2] = 255 * data2[i+2] / 0xFF;

Sekarang, seseorang hanya perlu menempelkan dua iPhone ke dahinya, mengklik "rekam video" secara bersamaan, dan kita dapat membuat film 3D sendiri dalam HTML5. Ada yang mau menjadi sukarelawan?

kacamata 3d

Neon-rainbow, zebra-reflection—efek berantai

Merangkai beberapa efek di <canvas> dapat dilakukan dengan mudah, tetapi pengetahuan dasar tentang globalCompositeOperation (GCO) diperlukan. Untuk membandingkan operasi dengan GIMP (atau Photoshop): ada 12 GCO di <canvas> darker, dan lighter dapat dianggap sebagai mode penggabungan lapisan; 10 operasi lainnya diterapkan ke lapisan sebagai mask alfa (satu lapisan menghapus piksel lapisan lainnya). globalCompositeOperation mengikat "lapisan" (atau dalam kasus kita, string kode) bersama-sama, menggabungkannya dengan cara baru dan menarik:

Grafis efek rantai

Diagram globalCompositeOperation menunjukkan mode GCO yang berfungsi; diagram ini menggunakan sebagian besar spektrum warna dan beberapa tingkat transparansi alfa untuk melihat secara mendetail apa yang diharapkan. Sebaiknya lihat referensi globalCompositeOperation Mozilla untuk deskripsi tekstual. Untuk riset lebih lanjut, Anda dapat mempelajari cara kerja operasi dalam Compositing Digital Images Porter Duff.

Mode favorit saya adalah globalCompositeOperation="lighter". Lighter memadukan piksel yang ditambahkan mirip dengan cara cahaya berpadu; saat cahaya merah, hijau, dan putih berada pada intensitas penuh, kita akan melihat cahaya putih. Fitur ini menarik untuk dicoba, terutama jika <canvas> ditetapkan ke globalAlpha yang rendah; memungkinkan kontrol yang lebih baik, dan tepi yang lebih halus. Lighter telah digunakan untuk banyak hal, favorit saya baru-baru ini adalah pembuat latar belakang desktop HTML5 yang dapat ditemukan di http://weavesilk.com/. Salah satu demo saya, Breathing Galaxies (JS1k), juga menggunakan mode lighter - dengan menggambar pola dari kedua contoh ini, Anda akan mulai melihat efek yang dihasilkan mode ini.

Penanganan browser globalCompositeOperation.

Efek jitter neon-pelangi

Dalam demo berikut, kita akan mendapatkan cahaya pelangi neon seperti Photoshop dengan garis tepi yang bergetar, dengan menggabungkan efek menggunakan globalCompositeOperation (source-in, lighter, dan darker). Demo ini merupakan pengembangan dari demo "Bayangan Teks di <canvas>", yang menggunakan strategi yang sama dalam memisahkan bayangan dari teks (lihat bagian sebelumnya):

Rainbow Jitter
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();
};

Efek pantulan zebra

Efek Zebra Reflection terinspirasi oleh referensi luar biasa dari WebDesignerWall tentang cara memperindah halaman Anda dengan CSS. Hal ini membawa ide tersebut sedikit lebih jauh, menciptakan "refleksi" untuk teks - seperti yang mungkin Anda lihat di iTunes. Efek ini menggabungkan fillColor (putih), createPattern (zebra.png), dan linearGradient (kilau); ini menggambarkan kemampuan untuk menerapkan beberapa jenis isian ke setiap objek vektor:

Efek zebra
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();
};

Bayangan dalam/luar di kanvas

Spesifikasi <canvas> tidak membahas subjek bayangan "dalam" vs. "luar". Bahkan, pada tampilan pertama, Anda mungkin mengira bayangan "dalam" tidak didukung. Bukan itu masalahnya. Hanya saja, mengaktifkannya sedikit lebih rumit ;) Seperti yang diusulkan dalam postingan terbaru dari F1LT3R, Anda dapat membuat bayangan dalam menggunakan properti unik dari aturan winding searah jarum jam vs. berlawanan arah jarum jam. Untuk melakukannya, buat "bayangan dalam" dengan menggambar persegi panjang penampung, lalu, menggunakan aturan winding yang berlawanan, gambar bentuk potongan - membuat kebalikan dari bentuk tersebut.

Contoh berikut memungkinkan inner-shadow dan fillStyle diberi gaya dengan warna+gradien+pola secara bersamaan. Anda dapat menentukan rotasi pola satu per satu; perhatikan bahwa garis zebra kini saling tegak lurus. Masker kliping seukuran kotak pembatas digunakan untuk menghilangkan kebutuhan akan penampung super besar untuk menutupi bentuk potongan - meningkatkan kecepatan dengan mencegah bagian bayangan yang tidak perlu diproses.

Bayangan dalam/luar
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();
};

Dari contoh ini, Anda dapat melihat bahwa dengan menggunakan globalCompositeOperation, kita dapat menggabungkan efek, sehingga menghasilkan efek yang lebih rumit (menggunakan masking dan penggabungan). Layar adalah milik Anda ;)

Spaceage—efek generatif

Di <canvas>, dari karakter unicode 0x2708:

Grafis Unicode

…hingga contoh yang diarsir ini:

Contoh yang diarsir

…dapat dicapai dengan beberapa panggilan ke ctx.strokeText() dengan lineWidth tipis (0,25), sambil perlahan mengurangi x-offset dan alfa; memberikan kesan gerakan pada elemen vektor kita.

Dengan memetakan posisi XY elemen ke gelombang sinus/kosinus, dan mengulangi warna menggunakan properti HSL, kita dapat membuat efek yang lebih menarik, seperti contoh "biohazard" ini:

Efek siklus HSL

HSL: Hue, Saturation, Lightness (1978)

HSL adalah format yang baru didukung dalam spesifikasi CSS3. Jika HEX dirancang untuk komputer, HSL dirancang agar dapat dibaca manusia.

Mengilustrasikan kemudahan HSL; untuk beralih melalui spektrum warna, kita cukup meningkatkan "hue" dari 360; hue dipetakan ke spektrum dalam bentuk silinder. Kecerahan mengontrol seberapa gelap/terang warna; 0% menunjukkan piksel hitam, sedangkan 100% menunjukkan piksel putih. Saturasi mengontrol seberapa terang atau cerah suatu warna; warna abu-abu dibuat dengan saturasi 0%, dan warna cerah dibuat menggunakan nilai 100%.

Grafik HSL

Karena HSL adalah standar terbaru, Anda mungkin ingin terus mendukung browser lama, yang dapat dilakukan melalui konversi ruang warna. Kode berikut menerima objek HSL { H: 360, S: 100, L: 100} dan menghasilkan objek RGB { R: 255, G: 255, B: 255 }. Dari sana, Anda dapat menggunakan nilai tersebut untuk membuat string rgb atau rgba. Untuk informasi yang lebih mendalam, lihat artikel Wikipedia yang informatif tentang 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
};
};

Membuat animasi dengan requestAnimationFrame

Sebelumnya, untuk membuat animasi di Javascript, ada dua pilihan; setTimeout, dan setInterval.

window.requestAnimationFrame, adalah standar baru di sini untuk menggantikan keduanya; menghemat listrik dunia (dan beberapa detak jantung komputer Anda) dengan memungkinkan browser mengatur animasi berdasarkan resource yang tersedia. Beberapa fitur penting mencakup:

  • Saat pengguna keluar dari frame, animasi dapat melambat atau berhenti sepenuhnya, untuk mencegah penggunaan resource yang tidak diperlukan.
  • Ada batas kecepatan frame sebesar 60 FPS. Alasannya adalah kecepatan ini jauh di atas tingkat yang dapat dilihat manusia (kebanyakan manusia pada 30 FPS melihat animasi sebagai "lancar").

Saat artikel ini ditulis, awalan khusus vendor diperlukan untuk menggunakan requestAnimationFrame. Paul Irish membuat lapisan shim yang memiliki dukungan lintas vendor, di requestAnimationFrame untuk animasi cerdas:

// 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);
        };
})();

Lebih jauh lagi, orang yang lebih ambisius mungkin akan mengaitkannya dengan poly-fill seperti requestAnimationFrame.js (ada beberapa fitur yang harus diselesaikan) yang akan mendukung browser lama lebih jauh, sekaligus beralih ke standar baru ini.

(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);
})();
Gambar Buram Catatan
Grafik Animasi
Grafis Matriks

Kode sumber

Dengan dukungan dari berbagai vendor browser, tidak ada keraguan tentang masa depan. <canvas> dapat di-porting ke file yang dapat dieksekusi di iPhone/Android/Desktop menggunakan PhoneGap, atau

Titanium.