เอฟเฟ็กต์ของแบบตัวอักษรใน Canvas

ดีลโดยไมเคิล
ดีลสำหรับไมเคิล

พื้นหลังของฉัน

<canvas> เข้าสู่การรับรู้เมื่อปี 2006 เมื่อมีการเปิดตัว Firefox v2.0 บทความเกี่ยวกับ Ajaxian ซึ่งอธิบายเกี่ยวกับเมทริกซ์การเปลี่ยนรูปแบบ ทำให้ฉันเกิดแรงบันดาลใจในการสร้างเว็บแอป <canvas> ชื่อว่า Color Sphere (2007) เป็นครั้งแรก ซึ่งทำให้ฉันดื่มด่ำไปกับโลกแห่งสีและกราฟิกเบื้องต้น เป็นแรงบันดาลใจในการสร้าง Sketchpad (2007-2008) เพื่อใส่แอปพลิเคชัน "ดีกว่า Paint" ไว้ในเบราว์เซอร์ ท้ายที่สุด การทดลองเหล่านี้นำไปสู่การสร้างสตาร์ทอัพอย่าง Mugtug ร่วมกับ Charles Pritchard เพื่อนเก่าของผม เรากำลังพัฒนา Darkroom ใน HTML5 <canvas> Darkroom เป็นแอปแชร์รูปภาพที่ไม่ทำลายสิ่งต่างๆ เพื่อรวมพลังของฟิลเตอร์แบบพิกเซลเข้ากับการพิมพ์และการวาดภาพแบบเวกเตอร์

เกริ่นนำ

กราฟิกแบนเนอร์ Canvas

<canvas> ช่วยให้โปรแกรมเมอร์ JavaScript ควบคุมสี เวกเตอร์ และพิกเซลบนหน้าจอได้อย่างเต็มที่

ตัวอย่างต่อไปนี้เกี่ยวข้องกับพื้นที่ 1 รายการใน <canvas> ที่ไม่ได้รับความสนใจมากนัก ซึ่งทำให้เกิดเอฟเฟกต์ข้อความ เอฟเฟกต์ข้อความที่สร้างขึ้นใน <canvas> มีความหลากหลายมากเท่าที่คุณจินตนาการได้ การสาธิตเหล่านี้ครอบคลุมส่วนย่อยของสิ่งที่เป็นไปได้ แม้ว่าเราจะพูดถึง "ข้อความ" ในบทความนี้ แต่คุณก็สามารถใช้เมธอดกับวัตถุเวกเตอร์ การสร้างภาพที่น่าตื่นเต้นในเกม และแอปพลิเคชันอื่นๆ ได้ ดังนี้

เงาข้อความใน <canvas>
เอฟเฟกต์ข้อความแบบ CSS ใน <canvas> ที่สร้างการตัดมาสก์ ค้นหาเมตริกใน <canvas> และใช้พร็อพเพอร์ตี้เงา
เอฟเฟกต์สายรุ้งแบบนีออน ภาพสะท้อนของม้าลาย - ห่วงโซ่
เอฟเฟกต์ข้อความแบบเหมือน Photoshop ใน <canvas> ตัวอย่างการใช้ GlobalCompositeOperating, createเชิงเส้นGradient, createPattern
เงาภายในและภายนอกใน <canvas>
แสดงคุณลักษณะที่ไม่ค่อยเป็นที่รู้จัก โดยใช้การขดตามเข็มนาฬิกาเทียบกับทวนเข็มนาฬิกาเพื่อสร้างเงาตกกระทบ (เงาภายใน)
อวกาศ - เอฟเฟกต์ Generative
เอฟเฟกต์ข้อความแบบ Generative ใน <canvas> โดยใช้ hsl() color-cycle และ window.requestAnimationFrame เพื่อสร้างความรู้สึกของการเคลื่อนไหว

เงาข้อความใน Canvas

สิ่งที่ผมชอบมากอย่างหนึ่งในข้อกำหนดของ CSS3 (รวมถึงเส้นขอบ-รัศมี การไล่ระดับสีเว็บ และอื่นๆ) คือความสามารถในการสร้างเงา สิ่งสำคัญคือต้องตระหนักถึงความแตกต่างระหว่าง CSS กับ <canvas> โดยเฉพาะอย่างยิ่ง

CSS มี 2 วิธี ได้แก่ box-shadow สำหรับองค์ประกอบของกล่อง เช่น div, span และอื่นๆ และใช้ text-shadow สำหรับเนื้อหาข้อความ

<canvas> มีเงาประเภทหนึ่ง ซึ่งใช้สำหรับออบเจ็กต์เวกเตอร์ทั้งหมด เช่น ctx.moveTo, ctx.lineTo, ctx.bezierCurveTo, ctx.quadradicCurveTo, ctx.arc, ctx.rect, ctx.fillText, ctx.stageText และอื่นๆ หากต้องการสร้างเงาใน <canvas> ให้แตะคุณสมบัติ 4 ข้อต่อไปนี้

ctx.shadowColor = "สีแดง" // สตริง
สีของเงาคือ RGB, RGBA, HSL, HEX และอินพุตอื่นๆ ที่ถูกต้อง
ctx.shadowOffsetX = 0; // จำนวนเต็ม
ระยะห่างในแนวนอนของเงาโดยสัมพันธ์กับข้อความ
ctx.shadowOffsetY = 0; // จำนวนเต็ม
ระยะห่างในแนวตั้งของเงาโดยสัมพันธ์กับข้อความ
ctx.shadowBlur = 10; // จำนวนเต็ม
เอฟเฟกต์การเบลอกับเงา ค่ายิ่งมากเท่าใด ความเบลอก็จะยิ่งมากขึ้นเท่านั้น

ในการเริ่มต้น มาดูกันว่า <canvas> สามารถจำลองเอฟเฟกต์ CSS ได้อย่างไร การค้นหา "เงาข้อความ css" ผ่านรูปภาพต่างๆ ของ Google ทำให้เราได้มีการสาธิตที่ดีตัวอย่าง เช่น Line25 และ Stereoscopic และ Shadow 3D

กราฟิก 3D ของ CSS

เอฟเฟกต์ 3 มิติสามมิติ (ดูข้อมูลเพิ่มเติมในรูปภาพ Anaglyph) เป็นตัวอย่างของบรรทัดโค้ดง่ายๆ ที่ใช้งานได้ดี ด้วยบรรทัด CSS ต่อไปนี้ เราสามารถสร้างภาพลวงตาว่าภาพมีความลึกเมื่อดูด้วยแว่นตาสีแดง/เขียว 3 มิติ (แบบที่เห็นในภาพยนตร์ 3 มิติ)

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

มี 2 สิ่งที่ควรสังเกตเมื่อแปลงสตริงนี้เป็น <canvas>:

  1. ไม่มีการเบลอเงา (ค่าที่ 3) จึงไม่มีเหตุผลที่จะใช้เงาจริง เนื่องจาก fillText จะสร้างผลลัพธ์เดียวกัน
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 ไม่รองรับใน <canvas> จึงต้องแปลงเป็น PX เราหาอัตราส่วน Conversion สำหรับการแปลงระหว่าง PT, PC, EM, EX, PX และอื่นๆ ได้โดยสร้างองค์ประกอบที่มีพร็อพเพอร์ตี้แบบอักษรเดียวกันใน DOM และตั้งค่าความกว้างเป็นรูปแบบที่จะวัด หรือถ้าจะบันทึก Conversion EM -> PX เราจะวัดองค์ประกอบ DOM ด้วย "ความสูง: 1em" ค่าออฟเซ็ตแต่ละค่าใน PX แต่ละค่าจะมีค่าออฟเซ็ตเท่าใด
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>

การป้องกันการคูณอัลฟ่า

ในตัวอย่างที่ซับซ้อนยิ่งขึ้น เช่น เอฟเฟกต์นีออนที่พบใน Line25 คุณต้องใช้พร็อพเพอร์ตี้ shadowBlur เพื่อจำลองเอฟเฟกต์ให้ถูกต้อง เนื่องจากเอฟเฟกต์นีออนอาศัยเงาหลายเงา เราจึงพบปัญหาใน <canvas> วัตถุเวกเตอร์แต่ละวัตถุจะมีเงาได้เพียงรายการเดียว ดังนั้นในการที่จะวาดเงาหลายๆ เงา คุณต้องวาดข้อความหลายๆ เวอร์ชันไว้ซ้อนทับกัน ซึ่งส่งผลให้เกิดการคูณจำนวนอัลฟ่าและขอบหยักในท้ายที่สุด

กราฟิกนีออน

ฉันลองเรียกใช้ ctx.fillStyle = "rgba(0,0,0,0)" หรือ "transparent" เพื่อซ่อนข้อความในขณะที่แสดงเงา... แต่ความพยายามนี้ไม่มีประโยชน์ เนื่องจากเงาเป็นการคูณของอัลฟ่า FillStyle เงาต้องไม่ทึบแสงมากกว่า FillStyle

โชคดีที่มีวิธีเช่นนี้ตรงที่เราสามารถวาดระยะห่างของเงาจากข้อความ ทำให้ข้อความไม่ซ้อนทับกัน (เพื่อไม่ให้ทับซ้อนกัน) และเป็นการซ่อนข้อความออกจากด้านข้างของหน้าจอ

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

การตัดไปรอบๆ บล็อกข้อความ

ในการแก้ไขเบื้องต้นนี้ เราสามารถป้องกันไม่ให้วาดfillText ขึ้นมาในตอนแรก (และเปิดโอกาสให้วาดเงา) ด้วยการเพิ่มเส้นทางการตัดขึ้นบรรทัดใหม่ เราจะต้องทราบความสูงของข้อความ (เรียกว่า "em-height" ตามความสูง ของตัวอักษร "M" บนแท่นพิมพ์) และความกว้างของข้อความ เพื่อสร้างเส้นทางการตัดที่ล้อมรอบข้อความ เราหาความกว้างได้โดยใช้ ctx.measureText().width แต่ ctx.measureText().height ไม่มีอยู่

โชคดีที่ด้วยกลยุทธ์การแฮ็ก CSS (ดูเมตริกการพิมพ์ สำหรับวิธีอื่นๆ ในการแก้ไขการใช้งาน <canvas> ในแบบเดิมโดยใช้การวัด CSS) เราสามารถหาความสูงของข้อความได้จากการวัด offsetHeight ของ <span> ที่มีพร็อพเพอร์ตี้แบบอักษรเดียวกัน

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

จากจุดนั้น เราจะสร้างสี่เหลี่ยมผืนผ้าเพื่อใช้เป็นเส้นทางตัดออก ล้อมรอบ "เงา" พร้อมนำรูปร่างจำลองออก

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

นำทุกอย่างมาต่อกันและเพิ่มประสิทธิภาพในขณะที่เราทำตาม เช่น หากเงาไม่เบลอ สามารถใช้คำว่า fillText ให้ผลได้เหมือนกัน ช่วยป้องกันไม่ให้เราตั้งค่ามาสก์แบบตัดออก

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

เนื่องจากคุณคงไม่อยากป้อนคำสั่ง <canvas> เหล่านี้เองทั้งหมด เราจึงได้รวมโปรแกรมแยกวิเคราะห์เงาข้อความแบบง่ายไว้ในแหล่งที่มาของการสาธิตแล้ว ซึ่งจะช่วยให้ผู้ใช้ป้อนคำสั่ง CSS ให้ฟีดและสร้างคำสั่ง <canvas> ได้ ตอนนี้องค์ประกอบ <canvas> ของเรามีหลากหลายสไตล์ที่นำมารวมได้ เอฟเฟกต์เงาเดียวกันนี้สามารถใช้กับออบเจ็กต์เวกเตอร์ใดก็ได้ ตั้งแต่ WebFonts รูปร่างที่ซับซ้อนที่นำเข้าจาก SVG ไปจนถึงรูปร่างเวกเตอร์ Generative และอื่นๆ

เงาข้อความในเอฟเฟกต์ภาพพิมพ์แคนวาส

ช่วงการฝึก (แทนเจนต์ในการพุชพิกเซล)

ในการเขียนส่วนนี้ ตัวอย่างสามมิติทำให้ฉันเกิดความสงสัย การสร้างเอฟเฟกต์หน้าจอภาพยนตร์ 3 มิติโดยใช้ <canvas> และภาพ 2 ภาพที่ถ่ายจากมุมมองที่ต่างกันเล็กน้อยนั้นยากเพียงใด เห็นได้ชัดว่าไม่ยากเกินไป เคอร์เนลต่อไปนี้รวมช่องสีแดงของรูปภาพแรก (ข้อมูล) กับช่องทางสีน้ำเงินของรูปภาพที่ 2 (data2) แล้ว

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

ตอนนี้ แค่มีบางคนติดเทปคาน iPhone 2 เครื่องไว้ที่หน้าผาก คลิก "บันทึกวิดีโอ" ในเวลาเดียวกัน เราก็สามารถสร้างภาพยนตร์ 3 มิติของเราเองใน HTML5 ได้ มีใครอยากอาสาไหม

แว่นตา 3 มิติ

เอฟเฟกต์สายรุ้งแบบนีออน การสะท้อนแสงของม้าลาย - ห่วงโซ่เอฟเฟกต์

การเชื่อมโยงผลกระทบหลายรายการใน <canvas> นั้นทำได้ง่าย แต่ต้องมีความรู้เบื้องต้นเกี่ยวกับ GlobalCompositeOperating (GCO) หากต้องการเปรียบเทียบการดำเนินการกับ GIMP (หรือ Photoshop) จะมี GCO 12 แบบใน<canvas> มืด และมืดอาจมองได้ว่าเป็นโหมดการรวมเลเยอร์ การดำเนินการอีก 10 รายการจะใช้กับเลเยอร์เป็นมาสก์อัลฟ่า (เลเยอร์หนึ่งจะนำพิกเซลของเลเยอร์อื่นออก) GlobalCompositeOperating จะเชื่อมโยง "เลเยอร์" (หรือในกรณีของเรา คือสตริงโค้ด) เข้าด้วยกัน ซึ่งเป็นการรวมเลเยอร์ในรูปแบบใหม่ๆ ที่น่าตื่นเต้น

กราฟิกเอฟเฟกต์ห่วงโซ่

แผนภูมิ GlobalCompositeยกเว้น แสดงโหมด GCO ในการทํางาน แผนภูมินี้ใช้สเปกตรัมสีส่วนใหญ่และความโปร่งใสของอัลฟ่าหลายระดับ เพื่อดูรายละเอียดสิ่งที่จะเกิดขึ้น ขอแนะนำให้ดูคำอธิบายแบบข้อความในข้อมูลอ้างอิงglobalCompositeOperating ของ Mozilla หากดูข้อมูลเพิ่มเติม คุณจะศึกษาวิธีการทำงานของการดำเนินการดังกล่าวได้ในภาพดิจิทัลแบบผสมของ Porter Duff

โหมดโปรดของฉันคือ GlobalCompositeActions="lighter" สีอ่อนจะผสมพิกเซลที่ต่อท้ายแบบเดียวกับการผสมแสง เมื่อแสงสีแดง เขียว และขาวที่ความเข้มเต็มของเรา เราจะเห็นแสงสีขาว นี่เป็นฟีเจอร์น่าลองเล่นดู โดยเฉพาะเมื่อตั้งค่า <canvas> เป็นรุ่นอัลฟ่าทั่วโลกต่ำ ทำให้ควบคุมได้ละเอียดยิ่งขึ้นและได้ขอบราบรื่นขึ้น มีการใช้งานมากมายมาก เกมล่าสุดของฉันคือ ครีเอเตอร์พื้นหลังเดสก์ท็อป HTML5 ที่ http://weavesilk.com/ ตัวอย่างหนึ่งของผมที่ชื่อว่า Breathing Galaxies (JS1k) ก็ใช้โหมดไฟแช็กเช่นกัน คือการวาดรูปแบบจาก 2 ตัวอย่างเหล่านี้ คุณเริ่มเห็นว่าโหมดนี้ให้ผลอย่างไร

การจัดการเบราว์เซอร์globalCompositeOperating

เอฟเฟกต์ Jitter สีนีออนสีรุ้ง

ในการสาธิตต่อไปนี้ เราจะได้แสงสีรุ้งที่เหมือน Photoshop พร้อมขอบกระตุก ด้วยการเชื่อมโยงเอฟเฟ็กต์เข้าด้วยกันโดยใช้ GlobalCompositeOperating (ซอร์สอิน สีอ่อน และสีเข้ม) การสาธิตนี้เป็นความคืบหน้าในการสาธิต "เงาข้อความใน <canvas>" โดยใช้กลยุทธ์เดียวกันในการแยกเงาออกจากข้อความ (ดูส่วนก่อนหน้า) ดังนี้

เสียงสะท้อนสีรุ้ง
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();
};

เอฟเฟกต์การสะท้อนของม้าลาย

เอฟเฟกต์ Zebra Reflection ได้แรงบันดาลใจมาจากแหล่งข้อมูลที่ยอดเยี่ยมของ WebDesignerWall เกี่ยวกับวิธีเติมสีสันให้กับหน้าเว็บด้วย CSS วิธีนี้ทำให้แนวคิดเพิ่มเติมเล็กน้อย เพื่อสร้าง "การสะท้อน" ให้กับข้อความ เช่น สิ่งที่คุณอาจเห็นใน iTunes เอฟเฟกต์นี้รวม fillColor (สีขาว), createPattern (zebra.png) และ linearGradient (shine) เข้าด้วยกัน ซึ่งจะแสดงให้เห็นถึงความสามารถในการใช้การเติมสีหลายประเภทกับวัตถุเวกเตอร์แต่ละรายการ

เอฟเฟกต์ม้าลาย
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();
};

เงาด้านใน/ด้านนอกใน Canvas

ข้อมูลจำเพาะของ <canvas> ไม่สัมผัสกับเงาที่ "ภายใน" กับเงา "ด้านนอก" อันที่จริง การปรากฏครั้งแรกคุณอาจไม่รองรับการใช้เงา "ภายใน" ซึ่งแท้จริงแล้ว ไม่ได้เป็นเช่นนั้น แต่เปิดใช้งานได้ยากกว่าที่เคย ;) ตามที่ได้เสนอไว้ในโพสต์ล่าสุดจาก F1LT3R คุณสร้างเงาภายในได้โดยใช้คุณสมบัติเฉพาะของกฎการขดตามเข็มนาฬิกาเทียบกับกฎการขดทวนเข็มนาฬิกา ในการทำเช่นนั้น คุณต้องสร้าง "เงาภายใน" โดยการวาดรูปสี่เหลี่ยมผืนผ้าของคอนเทนเนอร์ แล้ววาดรูปร่างคดเคี้ยวตรงกันข้ามโดยใช้กฎการขดที่ตัดกัน

ตัวอย่างต่อไปนี้ช่วยให้เงาภายในและfillStyle เป็นสไตไลซ์ด้วยสี+การไล่ระดับสี+รูปแบบพร้อมกัน คุณระบุการหมุนรูปแบบทีละรายการได้ สังเกตว่าแถบม้าลายตั้งฉากกับแต่ละเส้น มีการใช้มาสก์ตัดออกขนาดของกรอบล้อมรอบโดยทำให้ไม่ต้องใช้คอนเทนเนอร์ขนาดใหญ่มากมาล้อมรูปร่างที่ถูกตัดออก ซึ่งจะช่วยเร่งความเร็วด้วยการป้องกันส่วนที่ไม่จำเป็นของเงาไม่ให้ได้รับการประมวลผล

เงาด้านใน/ด้านนอก
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();
};

จากตัวอย่างต่อไปนี้ การใช้ GlobalCompositeOps ช่วยให้เราเชื่อมโยงเอฟเฟกต์เข้าด้วยกันเพื่อสร้างเอฟเฟกต์ได้ละเอียดยิ่งขึ้น (ใช้การมาสก์และการผสม) หน้าจอนี้ใช่หอยนางรมเลย ;)

อวกาศ - เอฟเฟกต์จาก Generative AI

ใน <canvas> จะขึ้นต้นด้วยอักขระ Unicode 0x2708 ดังนี้

กราฟฟิกแบบยูนิโค้ด

...ในตัวอย่างที่แรเงานี้:

ตัวอย่างแบบแรเงา

...สามารถทำได้โดยการเรียก ctx.strokeText() หลายครั้งด้วยความกว้างเส้นบาง (0.25) ขณะที่ค่อยๆ ลดค่าออฟเซ็ต X และอัลฟ่าลงช้าๆ ทำให้องค์ประกอบเวกเตอร์ของเรารู้สึกถึงการเคลื่อนไหว

การจับคู่ตำแหน่งองค์ประกอบ XY กับคลื่นไซน์/โคไซน์ และวนสีโดยใช้คุณสมบัติ HSL ทำให้เราสามารถสร้างเอฟเฟ็กต์ที่น่าสนใจยิ่งขึ้นได้ ดังตัวอย่างต่อไปนี้

เอฟเฟกต์การปั่นจักรยาน HSL

HSL: โทนสี ความอิ่มตัว ความสว่าง (1978)

HSL เป็นรูปแบบใหม่ที่รองรับในข้อกำหนดของ CSS3 ในขณะที่ HEX ออกแบบมาสำหรับคอมพิวเตอร์ HSL ออกแบบมาเพื่อให้มนุษย์อ่านได้

เพื่อแสดงให้เห็นถึงความง่ายของ HSL ในการวนรอบสี เรา "เพิ่ม "hue" จาก 360 โทนสีจะถูกจับคู่กับสเปกตรัมในสไตล์ทรงกระบอก ความสว่างจะควบคุมความมืด/ความสว่างของสี 0% หมายถึงพิกเซลสีดำ ขณะที่ 100% หมายถึงพิกเซลสีขาว ความอิ่มตัวจะควบคุมระดับความสว่างหรือความสดใสของสี สีเทาคือความอิ่มตัว 0% และสีที่สดใสจะถูกสร้างโดยใช้ค่า 100%

กราฟิก HSL

เนื่องจาก HSL เป็นมาตรฐานล่าสุด คุณจึงอาจต้องการสนับสนุนเบราว์เซอร์รุ่นเก่าต่อไป ซึ่งสามารถทำได้ผ่านการแปลงพื้นที่สี โค้ดต่อไปนี้ยอมรับ ออบเจ็กต์ HSL { H: 360, S: 100, L: 100} และเอาต์พุตเป็นออบเจ็กต์ RGB { R: 255, G: 255, B: 255 } จากที่นั่น คุณสามารถใช้ค่าเหล่านั้นเพื่อสร้างสตริง RGB หรือ RGBa ได้ ดูบทความเชิงลึกของ HSL จาก Wikipedia เพื่อให้ทราบข้อมูลเชิงลึกเพิ่มเติม

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

การสร้างภาพเคลื่อนไหวด้วย requestAnimationFrame

ก่อนหน้านี้ หากต้องการสร้างภาพเคลื่อนไหวใน JavaScript จะมี 2 ตัวเลือก ได้แก่ setTimeout และ setInterval

window.requestAnimationFrame คือมาตรฐานใหม่ที่จะใช้แทนทั้ง 2 ฟีเจอร์ ซึ่งก็คือการช่วยประหยัดพลังงานไฟฟ้าของโลก (และทำให้คอมพิวเตอร์มีฮาร์ตบีตไม่กี่ครั้ง) โดยอนุญาตให้เบราว์เซอร์ควบคุมภาพเคลื่อนไหวตามทรัพยากรที่มีอยู่ ฟีเจอร์สำคัญบางส่วนมีดังนี้

  • เมื่อมีผู้ใช้อยู่ในเฟรม ภาพเคลื่อนไหวอาจช้าลงหรือหยุดไปเลย เพื่อป้องกันไม่ให้มีการใช้ทรัพยากรที่ไม่จำเป็น
  • มีการจำกัดอัตราเฟรมไว้ที่ 60 FPS ด้วยเหตุผลนี้ ภาพเคลื่อนไหวนี้ อยู่ในระดับที่อยู่เหนือระดับที่มนุษย์จะสังเกตเห็นได้ (มนุษย์ส่วนใหญ่ถึง 30FPS จะเห็นภาพเคลื่อนไหว "แบบไหล")

เวลาที่เขียน ต้องใส่คำนำหน้าที่เฉพาะเจาะจงเพื่อใช้ requestAnimationFrame Paul Ireland สร้างเลเยอร์shim ที่มีการรองรับแบบครอสเวนเดอร์ใน requestAnimationFrame สำหรับ การสร้างภาพเคลื่อนไหวอัจฉริยะ ดังนี้

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

หากดำเนินการต่ออีกเล็กน้อย ผู้ใช้อาจมีความทะเยอทะยานมากขึ้นในการเชื่อมโยงกับ Poly-fill เช่น requestAnimationFrame.js (มีฟีเจอร์บางอย่างที่ต้องปรับปรุง) ซึ่งจะรองรับเบราว์เซอร์รุ่นเก่าขึ้นอีกขั้นในขณะที่เปลี่ยนไปใช้มาตรฐานใหม่นี้

(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);
})();
กราฟิกเบลอของโน้ต
กราฟิกแอนิเมชัน
กราฟิกเมทริกซ์

ซอร์สโค้ด

ด้วยการสนับสนุนจากทุกขอบเขตของผู้ให้บริการเบราว์เซอร์ คุณจึงไม่ต้องกังวลเกี่ยวกับอนาคตของ <canvas> ที่จะสามารถย้ายไปยังไฟล์ปฏิบัติการของ iPhone/Android/เดสก์ท็อป โดยใช้ PhoneGap หรือ

Titanium