כמה פיקסלים יש באמת בהדפסה על קנבס?
החל מגרסה 84 של Chrome, ResizeObserver תומך במדידת תיבה חדשה בשם devicePixelContentBox
, שמודדת את מידות הרכיב בפיקסלים פיזיים. כך ניתן לעבד גרפיקה מושלמת, במיוחד בהקשר של מסכים עם צפיפות פיקסלים.
תמיכה בדפדפן
- 84
- 84
- 93
- x
רקע: פיקסלים של CSS, פיקסלים על קנבס ופיקסלים פיזיים
בדרך כלל אנחנו עובדים עם יחידות אורך מופשטות כמו em
, %
או vh
, אבל מסתכמים בפיקסלים. בכל פעם שאנחנו מציינים את הגודל או המיקום של רכיב ב-CSS, בסופו של דבר מנוע הפריסה של הדפדפן ימיר את הערך הזה לפיקסלים (px
). אלה הם "פיקסלים של CSS", שיש להם היסטוריה רבה ויש להם קשר רופף רק לפיקסלים שיש לך במסך.
במשך תקופה ארוכה, היה סביר להעריך את דחיסות הפיקסלים של כל אחד במסך עם 96DPI ("נקודות לאינץ'"), כלומר, כל צג נתון יהיה בעל כ-38 פיקסלים לס"מ. עם הזמן, המסכים גדלו ו/או הצטמצמו או התחילו לכלול יותר פיקסלים על אותו שטח פנים. אם נשלב זאת עם העובדה שהרבה תכנים באינטרנט מגדירים את המידות שלהם, כולל גודל גופנים, ב-px
, מתקבל טקסט לא קריא במסכים עם צפיפות גבוהה ("HiDPI"). כאמצעי נגדי, הדפדפנים מסתירים את דחיסות הפיקסלים בפועל של הצג ובמקום זאת מעמידים פנים שלמשתמש יש מסך של 96 DPI. היחידה px
ב-CSS מייצגת את הגודל של פיקסל אחד במסך הווירטואלי הזה של 96 DPI, ומכאן השם "CSS Pixel". היחידה הזו משמשת רק למדידה ולמיקום. לפני שמתבצע רינדור בפועל, מתרחשת המרה לפיקסלים פיזיים.
איך עוברים מהתצוגה הווירטואלית הזו לתצוגה האמיתית של המשתמש? צריך להזין devicePixelRatio
. הערך הגלובלי הזה מציין כמה פיקסלים פיזיים אתם צריכים כדי ליצור פיקסל CSS יחיד. אם הערך של devicePixelRatio
(dPR) הוא 1
, המסך שלך הוא בערך 96DPI. אם יש לך מסך רטינה, סביר להניח שתאריך ה-dPR הוא 2
. בטלפונים, יכול להיות שערכי dPR גבוהים יותר (ומוזרים יותר) כמו 2
, 3
או אפילו 2.65
. חשוב לציין שהערך הזה הוא מדויק, אבל לא מאפשר לחלץ את ערך ה-DPI בפועל של הצג. dPR של 2
פירושו שפיקסל CSS אחד ימופה ל-2 פיקסלים פיזיים בדיוק.
1
לפי Chrome...רוחבו 3,440 פיקסלים ואזור התצוגה הוא ברוחב 79 ס"מ.
הרזולוציה הזו מובילה לרזולוציה של 110 DPI. קרוב ל-96, אבל לא בדיוק.
זו גם הסיבה לכך שגודל של <div style="width: 1cm; height: 1cm">
לא יימדד בדיוק של 1 ס"מ ברוב המסכים.
לסיום, גם תכונת המרחק מהתצוגה של הדפדפן יכולה להשפיע על dPR. אם מגדילים את התצוגה, הדפדפן מגדיל את ה-dPR המדווח וגורם להצגת כל התוכן. אם בודקים את devicePixelRatio
במסוף כלי הפיתוח תוך כדי התקרבות, ניתן לראות ערכים חלקיים.
עכשיו נוסיף את האלמנט <canvas>
לתמהיל. אפשר לציין כמה פיקסלים רוצים שיהיה באזור העריכה באמצעות המאפיינים width
ו-height
. לכן, <canvas width=40 height=30>
יהיה קנבס עם גודל של 40 על 30 פיקסלים. עם זאת, זה לא אומר שהוא יוצג בגודל של 40 על 30 פיקסלים. כברירת מחדל, הקנבס ישתמש במאפיינים width
ו-height
כדי להגדיר את הגודל הפנימי שלו, אבל אפשר לשנות את הגודל של אזור העריכה באופן שרירותי באמצעות כל מאפייני ה-CSS המוכרים והאהובים. על סמך כל מה שלמדנו עד עכשיו, יכול להיות שתראו שאין זה אידיאלי בכל אחד מהתרחישים האלה. פיקסל אחד בהדפסה על קנבס עשוי לכסות כמה פיקסלים פיזיים, או רק חלק קטן מפיקסל פיזי. דבר זה עלול להוביל לפריטי מידע ויזואליים לא רצויים.
לסיכום: לרכיבים של לוח הציור יש גודל נתון כדי להגדיר את האזור שאפשר לשרטט עליו. מספר הפיקסלים של קנבס לא תלוי לחלוטין בגודל התצוגה של אזור העריכה, שצוין בפיקסלים של CSS. מספר הפיקסלים ב-CSS לא זהה למספר הפיקסלים הפיזיים.
שלמות הפיקסלים
במקרים מסוימים, רצוי להשתמש במיפוי מדויק מפיקסלים של קנבס לפיקסלים פיזיים. אם תושג המיפוי הזה, הוא נקרא 'מושלם'. עיבוד מושלם של פיקסלים הוא חיוני לעיבוד קריא של טקסט, במיוחד בעת שימוש בעיבוד תת-פיקסלי או בעת הצגת גרפיקה עם קווים מיושרים היטב של בהירות מתחלפת.
כדי להשיג פריט קרוב ככל האפשר לדף ציור מושלם באינטרנט, נסחפת פחות או יותר הגישה הנוכחית:
<style>
/* … styles that affect the canvas' size … */
</style>
<canvas id="myCanvas"></canvas>
<script>
const cvs = document.querySelector('#myCanvas');
// Get the canvas' size in CSS pixels
const rectangle = cvs.getBoundingClientRect();
// Convert it to real pixels. Ish.
cvs.width = rectangle.width * devicePixelRatio;
cvs.height = rectangle.height * devicePixelRatio;
// Start drawing…
</script>
הקורא החכם עשוי לתהות מה קורה כש-dPR אינו ערך של מספר שלם. זו שאלה טובה, ואיפה בדיוק טמון שורש הבעיה. בנוסף, אם מציינים את המיקום או הגודל של רכיב מסוים באמצעות אחוזים, vh
או ערכים עקיפים אחרים, ייתכן שהשיוך שלהם ישתנה לערכי פיקסלים חלקיים של CSS. רכיב עם margin-left: 33%
יכול להסתיים במלבן כך:
פיקסלים ב-CSS הם וירטואליים לחלוטין, כך שבתיאוריה יש שברים של פיקסל, אבל איך הדפדפן מזהה את המיפוי לפיקסלים פיזיים? כי פיקסלים פיזיים חלקיים הם לא משהו.
הצמדת Pixel
החלק בתהליך המרת היחידה שמטפל ביישור אלמנטים עם פיקסלים פיזיים נקרא 'pixelSnapping', והוא עושה את מה שהוא אומר על הבדיל: הוא מצמיד ערכי פיקסלים חלקיים למספרים שלמים של ערכי פיקסלים פיזיים. האופן שבו זה קורה בדיוק שונה מדפדפן לדפדפן. אם יש במסך רכיב שהרוחב שלו הוא 791.984px
וה-dPR הוא 1, דפדפן אחד עשוי לעבד את הרכיב בגודל 792px
פיקסלים פיזיים, ואילו דפדפן אחר עשוי לבצע עיבוד ב-791px
. מדובר בפיקסל אחד בלבד, אבל פיקסל אחד עלול לפגוע בתמונות עיבוד שצריכות להיות מושלמות. כתוצאה מכך, התוצאה יכולה להיות טשטוש או חפצים בולטים יותר, כמו אפקט מוארה.
devicePixelContentBox
devicePixelContentBox
מציג את תיבת התוכן של רכיב ביחידות הפיקסלים של המכשיר (כלומר, פיקסלים פיזיים). זה חלק מ-ResizeObserver
. sizeObserver נתמך עכשיו בכל הדפדפנים המובילים מ-Safari 13.1, אבל נכון לעכשיו הנכס devicePixelContentBox
זמין רק ב-Chrome בגרסה 84 ומעלה.
כפי שצוין ב-ResizeObserver
: בדומה ל-document.onresize
לרכיבים, פונקציית הקריאה החוזרת של ResizeObserver
תופעל לפני הצגת תמונה ואחרי פריסה. פירוש הדבר הוא שהפרמטר entries
של הקריאה החוזרת (callback) יכלול את הגדלים של כל הרכיבים המתועדים ממש לפני הצביעה שלהם. בהקשר של בעיית הקנבס שתיארנו קודם, אנחנו יכולים לנצל את ההזדמנות הזו כדי לשנות את מספר הפיקסלים בהדפסה על קנבס, כדי להבטיח שבסופו של דבר יהיה מיפוי אחד-לאחד בין הפיקסלים של בד הציור לבין הפיקסלים הפיזיים.
const observer = new ResizeObserver((entries) => {
const entry = entries.find((entry) => entry.target === canvas);
canvas.width = entry.devicePixelContentBoxSize[0].inlineSize;
canvas.height = entry.devicePixelContentBoxSize[0].blockSize;
/* … render to canvas … */
});
observer.observe(canvas, {box: ['device-pixel-content-box']});
המאפיין box
באובייקט האפשרויות של observer.observe()
מאפשר להגדיר את הגדלים שרוצים לעקוב אחריהם. לכן, למרות שכל ResizeObserverEntry
תמיד יספק את borderBoxSize
, contentBoxSize
ו-devicePixelContentBoxSize
(בתנאי שהדפדפן תומך בכך), הקריאה החוזרת תופעל רק אם אחד מהמדדים של התיבה נצפו ישתנה.
בעזרת התכונה החדשה הזו, אנחנו יכולים אפילו להוסיף אנימציה לגודל ולמיקום של הקנבס (הבטחה בפועל לערכי פיקסלים חלקיים), בלי לראות השפעות של Moiré על הרינדור. רוצה לראות את השפעת מוארה על הגישה באמצעות getBoundingClientRect()
ואיך הנכס החדש ב-ResizeObserver
מאפשר להימנע מכך? כדאי לצפות בהדגמה של Chrome מגרסה 84 ואילך!
זיהוי תכונות
כדי לבדוק אם הדפדפן של המשתמש תומך ב-devicePixelContentBox
, אנחנו יכולים לראות רכיב כלשהו ולבדוק אם הנכס נמצא ב-ResizeObserverEntry
:
function hasDevicePixelContentBox() {
return new Promise((resolve) => {
const ro = new ResizeObserver((entries) => {
resolve(entries.every((entry) => 'devicePixelContentBoxSize' in entry));
ro.disconnect();
});
ro.observe(document.body, {box: ['device-pixel-content-box']});
}).catch(() => false);
}
if (!(await hasDevicePixelContentBox())) {
// The browser does NOT support devicePixelContentBox
}
סיכום
באופן מפתיע באינטרנט, פיקסלים הם נושא מורכב, ועד כה לא הייתה דרך לדעת מהו המספר המדויק של הפיקסלים הפיזיים שיש ברכיב על מסך המשתמש. המאפיין devicePixelContentBox
החדש ב-ResizeObserverEntry
מספק לך את פיסת המידע הזו ומאפשר לך ליצור רינדור פיקסלים בצורה מושלמת באמצעות <canvas>
. devicePixelContentBox
נתמך ב-Chrome 84 ומעלה.