Efek tipografi di kanvas

Latar Belakang Saya

<canvas> memasuki kesadaran saya pada tahun 2006 ketika Firefox v2.0 dirilis. Sebuah artikel tentang Ajaxian, yang menjelaskan matriks transformasi, menginspirasi saya untuk membuat aplikasi web <canvas> pertama saya; Color Sphere (2007). Yang membenamkan saya ke dunia warna dan primitif grafis; menginspirasi pembuatan Sketchpad (2007-2008) dalam upaya untuk membuat aplikasi "lebih baik daripada Paint" di browser. Eksperimen ini pada akhirnya melahirkan penciptaan startup Mugtug dengan teman lama saya, Charles Pritchard. Kami mengembangkan Darkroom di HTML5 <canvas>. Darkroom adalah aplikasi berbagi foto yang tidak merusak, yang menggabungkan kecanggihan filter berbasis piksel dengan tipografi dan gambar berbasis vektor.

Pengantar

Gambar banner kanvas.

<canvas> menghadirkan kontrol penuh warna, vektor, dan piksel di layar kepada programmer JavaScript - susunan visual monitor.

Contoh berikut menangani satu area di <canvas> yang belum banyak diperhatikan; membuat efek teks. Keragaman efek teks yang dapat dibuat di <canvas> seluas yang dapat Anda bayangkan - demo ini mencakup sub-bagian tentang kemungkinan. Meskipun kami menangani "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 seperti CSS di <canvas> membuat clipping mask, menemukan metrik dalam <canvas>, dan menggunakan properti bayangan.
Pelangi neon, pantulan zebra - efek berantai.
Efek teks mirip Photoshop dalam <canvas> contoh penggunaan globalCompositeOperation, createLinearGradient, createPattern.
Bayangan dalam dan luar di <canvas>
Mengungkap fitur yang kurang dikenal; menggunakan lilitan searah jarum jam vs. berlawanan arah jarum jam untuk menciptakan kebalikan drop-shadow (bayangan dalam).
Ruang angkasa - efek generatif.
Efek teks berbasis generatif di <canvas> menggunakan siklus warna hsl() dan window.requestAnimationFrame untuk menciptakan nuansa gerakan.

Bayangan Teks di Canvas

Salah satu tambahan favorit saya untuk spesifikasi CSS3 (beserta radius batas, gradien web, dan lainnya) adalah kemampuan untuk membuat bayangan. Penting untuk menyadari 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; ini digunakan untuk semua objek vektor; ()`.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 ini:

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

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

Grafis 3D CSS

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

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

Ada dua hal yang perlu diperhatikan saat mengonversi string ini menjadi <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 dalam <canvas> sehingga harus dikonversi ke PX. Kita dapat menemukan rasio konversi untuk konversi antara PT, PC, EM, EX, PX, dan seterusnya dengan membuat elemen dengan properti font yang sama di DOM, dan menyetel lebar ke format yang akan diukur; atau instance, untuk menangkap konversi EM -> PX, kita akan mengukur elemen DOM dengan "height: 1em", dan offsetHeight yang dihasilkan adalah berapa banyak 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

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

Gambar 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 merupakan perkalian alfa fillStyle, bayangan tidak akan pernah lebih buram daripada fillStyle.

Untungnya, ada cara untuk mengatasi ini, kita bisa menggambar offset bayangan dari teks, memisahkannya (agar tidak tumpang tindih), dan dengan itu menyembunyikan teks dari 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);

Klip di sekitar blok teks

Untuk membersihkannya, kita dapat mencegah fillText digambar sejak pertama (sekaligus membiarkan bayangan digambar) dengan menambahkan jalur pemotongan. Untuk membuat jalur pemotongan teks, kita perlu mengetahui tinggi teks (disebut "em-height" secara historis, 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 hack-ardry CSS (lihat Metrik Tipografi untuk cara lain memperbaiki implementasi lama <canvas> menggunakan pengukuran CSS), kita dapat menemukan tinggi teks melalui pengukuran offsetHeight <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 bisa membuat persegi panjang untuk digunakan sebagai jalur kliping; mengapit "bayangan" sambil menghilangkan bentuk tiruan.

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

Menggabungkan semuanya, dan mengoptimalkan saat kita berjalan - jika bayangan tidak memiliki blur, fillText dapat digunakan untuk efek yang sama, sehingga kita tidak perlu menyiapkan mask pemangkasan:

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 bayangan teks sederhana dalam sumber demo; dengan cara ini Anda dapat memberikan 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 WebFonts hingga bentuk kompleks yang diimpor dari SVG, bentuk vektor generatif, dan sebagainya.

Bayangan teks dalam efek kanvas

Intermisi (tangen pada pixel-push)

Saat menulis bagian artikel ini, contoh Stereoskopis membuat saya penasaran. Seberapa sulit untuk membuat efek layar film 3D menggunakan <canvas> dan dua gambar yang diambil dari perspektif yang sedikit berbeda? Tampaknya, tidak terlalu sulit. Kernel berikut menggabungkan saluran merah dari gambar pertama (data) dengan saluran sian dari 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, klik "rekam video" secara bersamaan, dan kita dapat membuat film 3D kita sendiri di HTML5. Ada sukarelawan?

Kacamata 3d

Pelangi neon, pantulan zebra—efek rantai

Membuat rantai 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 dalam <canvas> lebih gelap, dan lebih ringan dapat dianggap sebagai mode campuran lapisan; 10 operasi lainnya diterapkan ke lapisan sebagai mask alfa (satu lapisan menghapus piksel lapisan lainnya). globalCompositeOperation menggabungkan "lapisan" (atau dalam kasus ini, string kode) bersama-sama, menggabungkannya dengan cara yang baru dan menarik:

Membuat rantai grafis efek

Diagram globalCompositeOperation menunjukkan mode GCO yang sedang digunakan. Diagram ini menggunakan sebagian besar spektrum warna dan beberapa tingkat transparansi alfa untuk melihat secara detail apa yang akan terjadi. Sebaiknya periksa referensi globalCompositeOperation Mozilla untuk deskripsi tekstual. Untuk riset lebih lanjut, Anda dapat mempelajari cara kerja operasi tersebut dalam Compositing Digital Image oleh Porter Duff.

Mode favorit saya adalah globalCompositeOperation="lebih ringan". Lebih terang mencampur piksel yang ditambahkan mirip dengan bagaimana cahaya bercampur; saat cahaya merah, hijau, dan putih berada pada intensitas penuh, kita akan melihat cahaya putih. Ini adalah fitur yang menarik untuk dicoba, terutama saat <canvas> disetel ke globalAlpha yang rendah; memungkinkan kontrol yang lebih baik, dan tepi yang lebih halus. Lighter telah digunakan dalam 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 yang lebih terang - menggambar pola dari dua contoh ini, Anda akan mulai melihat efek yang dihasilkan mode ini.

penanganan browser globalCompositeOperation.

Efek Jitter Neon-Pelangi

Dalam demo berikut, kita akan mencapai glow neon-rainbow-mirip Photoshop dengan garis luar jitter, dengan merangkai efek bersama menggunakan globalCompositeOperation (source-in, lebih terang, dan lebih gelap). Demo ini adalah progres dari demo "Text-Shadows in <canvas>", yang menggunakan strategi yang sama dalam memisahkan bayangan dari teks (lihat bagian sebelumnya):

Jitter Pelangi
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 Refleksi Zebra

Efek Zebra Reflection terinspirasi oleh referensi bagus dari WebDesignerWall tentang cara membuat halaman Anda lebih menarik dengan CSS. Hal ini membawa ide tersebut sedikit lebih jauh, membuat "refleksi" untuk teks - seperti apa yang mungkin Anda lihat di iTunes. Efeknya menggabungkan fillColor (putih), createPattern (zebra.png), dan linearGradient (shine); ini mengilustrasikan 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 Canvas

Spesifikasi <canvas> tidak menyentuh subjek bayangan "dalam" vs. "luar". Bahkan, saat pertama kali muncul, Anda mungkin mengira bayangan "dalam" tidak didukung. Bukan itu masalahnya. Hanya sedikit lebih sulit untuk mengaktifkannya;) Seperti yang diusulkan dalam postingan terbaru dari F1LT3R, Anda dapat membuat bayangan dalam menggunakan properti unik, yaitu aturan putaran searah jarum jam vs. berlawanan arah jarum jam. Untuk melakukannya, buat "bayangan dalam" dengan menggambar persegi panjang container, lalu, menggunakan aturan pelilitan yang berlawanan, gambar bentuk potongan yang akan menghasilkan 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 sekarang tegak lurus satu sama lain. Masker klip seukuran kotak pembatas digunakan sehingga tidak memerlukan penampung super besar untuk mengapit bentuk potongan. Dengan ini, Anda dapat meningkatkan kecepatan dengan mencegah pemrosesan bagian bayangan yang tidak diperlukan.

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 berikut, dengan menggunakan globalCompositeOperation, kita dapat membuat efek berantai, sehingga menghasilkan efek yang lebih rumit (menggunakan masking dan pencampuran). Layarnya adalah tiram Anda ;)

Ruang angkasa—efek generatif

Di <canvas>, beralih dari karakter unicode 0x2708:

Gfafik unicode

...ke contoh berarsir berikut:

Contoh berbayang

...dapat dilakukan dengan beberapa panggilan ke ctx.strokeText() dengan lineWidth yang tipis (0,25), sekaligus mengurangi x-offset dan alfa secara perlahan; memberikan kesan bergerak pada elemen vektor kita.

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

Efek siklus HSL

HSL: Hue, Saturasi, Lightness (1978)

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

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

Grafik HSL

Karena HSL adalah standar terbaru, sebaiknya terus dukungan 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 mengetahui informasi yang lebih mendalam, lihat artikel informatif Wikipedia 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 menghemat beberapa detak jantung) dengan memungkinkan browser mengatur animasi berdasarkan resource yang tersedia. Beberapa fitur penting mencakup:

  • Saat pengguna ada dalam frame, animasi dapat melambat atau berhenti sepenuhnya, untuk mencegah penggunaan resource yang tidak diperlukan.
  • Ada batas kecepatan frame pada 60 FPS. Alasannya adalah karena jauh di atas level yang dapat dilihat manusia (kebanyakan manusia dengan kecepatan 30 FPS melihat animasi "fluid").

Pada saat penulisan, awalan tertentu diperlukan untuk menggunakan requestAnimationFrame. Paul Ireland 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, semakin ambisius Anda mungkin akan mengaitkannya dengan poly-fill seperti requestAnimationFrame.js (ada beberapa fitur yang perlu dikerjakan) yang akan mendukung browser lama hingga ke tingkat yang lebih tinggi, 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);
})();
Grafik Buram Catatan
Grafik Animasi
Grafik Matriks

Kode sumber

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

Titanium.