การเรนเดอร์ Canvas ที่คมชัดบนหน้าจอ High-DPI และ Retina
เหตุใดกราฟิกแคนวาสจึงดูพร่ามัวบนหน้าจอ Retina และหน้าจอ DPI สูง และวิธีแก้ไขด้วย devicePixelRatio รวมถึงการปรับขนาดการจัดการและการจำกัดประสิทธิภาพ
หลัก Takeaway
ผืนผ้าใบดูไม่ชัดเจนบนหน้าจอที่มีความหนาแน่นสูงเมื่อขนาดพิกเซลตรงกับขนาด CSS เนื่องจากแต่ละพิกเซลของผืนผ้าใบถูกขยายออกไปบนพิกเซลทางกายภาพหลายพิกเซล แก้ไขโดยตั้งค่าความกว้างและความสูงของผืนผ้าใบเป็นขนาด CSS คูณด้วย window.devicePixelRatio ทำให้ขนาด CSS ไม่เปลี่ยนแปลง และปรับขนาดบริบทการวาดภาพด้วยอัตราส่วนเดียวกัน อัปเดตเมื่อปรับขนาดและซูม
เหตุใดผืนผ้าใบจึงเบลอ
MDN อธิบาย devicePixelRatio ว่าเป็นอัตราส่วนของพิกเซลจริงต่อพิกเซล CSS บนจอแสดงผลปัจจุบัน โดย 1 บนหน้าจอ 96 DPI แบบคลาสสิก และ 2 บนหน้าจอ HiDPI หรือ Retina ทั่วไป แคนวาสมีบัฟเฟอร์พิกเซลของตัวเอง หากบัฟเฟอร์นั้นมีขนาด 300 x 150 พิกเซล แต่แสดงเป็น 300 x 150 CSS พิกเซลบนหน้าจอที่มีอัตราส่วน 2 เบราว์เซอร์จะขยายเกิน 600 x 300 พิกเซลจริง ซึ่งดูนุ่มนวล
การแก้ไข
แนวทางที่แนะนำของ MDN คือการใช้ devicePixelRatio เพื่อเพิ่มความหนาแน่นของพิกเซลเพิ่มเติม: ตั้งค่าบัฟเฟอร์แคนวาสเป็นขนาด CSS คูณด้วยอัตราส่วน รักษาขนาด CSS ให้เท่าเดิม และปรับขนาดบริบท 2D เพื่อให้โค้ดรูปวาดของคุณสามารถใช้พิกเซล CSS ต่อไปได้ ดูรหัสด้านล่าง
จัดการการปรับขนาดและซูม
อัตราส่วนจะเปลี่ยนไปเมื่อหน้าต่างย้ายไปยังจอภาพอื่นหรือผู้ใช้ซูมหน้า คำนวณการปรับขนาดอีกครั้ง และรับฟังการเปลี่ยนแปลงในแบบสอบถามสื่อความละเอียด เพื่อให้คุณสามารถวาดใหม่ด้วยความหนาแน่นใหม่ ResizeObserver บนองค์ประกอบ Canvas เป็นวิธีที่เชื่อถือได้ในการรักษาขนาดให้ซิงค์กับเค้าโครง
ขีดจำกัดประสิทธิภาพ
การเพิ่มอัตราส่วนเป็นสองเท่าจะทำให้พิกเซลที่จะวาดเพิ่มขึ้นสี่เท่า บนผืนผ้าใบขนาดใหญ่มากหรืออุปกรณ์ระดับล่าง ให้จำกัดอัตราส่วน เช่น 2 และวัดเวลาของเฟรม รันไทม์ของแอนิเมชันที่วาดไปยังแคนวาส เช่น ตัวเรนเดอร์แคนวาสของ Lottie, Rive และ ExodeUI จัดการการปรับขนาดนี้ให้กับคุณ แต่จะมีข้อดีข้อเสียเหมือนกัน
ตัวอย่างการใช้งานการผลิต
function setupCanvas(canvas) {
const ctx = canvas.getContext('2d');
function resize() {
const ratio = Math.min(window.devicePixelRatio || 1, 2); // cap for performance
const { width, height } = canvas.getBoundingClientRect(); // CSS size
canvas.width = Math.round(width * ratio); // backing store in device pixels
canvas.height = Math.round(height * ratio);
ctx.setTransform(ratio, 0, 0, ratio, 0, 0); // draw in CSS pixels
draw(ctx, width, height);
}
new ResizeObserver(resize).observe(canvas);
resize();
}
function draw(ctx, width, height) {
ctx.clearRect(0, 0, width, height);
ctx.lineWidth = 1;
ctx.strokeRect(0.5, 0.5, width - 1, height - 1); // crisp 1px border
}คีย์ Principles ที่ต้องจำ
- devicePixelRatio = พิกเซลจริงต่อ CSS พิกเซล (2 บนหน้าจอ Retina ทั่วไป)
- ตั้งค่า canvas.width/height เป็น CSS ขนาด × อัตราส่วน คงขนาด CSS เอาไว้
- ปรับขนาดบริบทเพื่อให้โค้ดการวาดใช้พิกเซล CSS
- คำนวณใหม่เมื่อปรับขนาด ตรวจสอบการเปลี่ยนแปลง และการซูม
- กำหนดอัตราส่วนบนผืนผ้าใบขนาดใหญ่เพื่อประสิทธิภาพ
คำถามที่พบบ่อย
แหล่งที่มา
- Window: devicePixelRatio property — MDN เว็บเอกสาร