These ten questions cover canvas behaviors that surprise experienced developers: a bitmap sized by CSS, a reset you did not ask for, paths that outlive a stroke, transforms that apply at different moments, shadows that ignore the scale, settings that are silently rejected, an image drawn too early, premultiplied alpha, a context hint given too late and a canvas that refuses to be read. Every snippet ran in Chrome 154 1 through the check.mjs script of Test Yourself!, which opens a page from disk, prints its console and saves a screenshot. Write down what each prints and why, then check Appendix H for the real results and the reasons.
Checking your answers
Run each page through check.mjs and read its console. Chrome also prints a warning that repeated getImageData() calls would be faster with willReadFrequently; it is advice, not an answer. For the second run of question 10, serve the folder and open the page in Chrome with its developer tools' console showing.
node check.mjs questions-1-5.html
node check.mjs questions-6-10.html
npx serve . # then open http://localhost:3000/questions-6-10.htmlQuestions
<!-- 1. A canvas sized only by CSS. What do width and height print, and what shape
does the circle take on screen? (Sections 2.1.1 and 2.1.3) -->
<canvas id="c1" style="width:600px; height:150px; outline:1px solid"></canvas>
<!-- 2. A red square, then width set to its own value, then a second square. What is
left, and in what color? (Section 2.1.2) -->
<canvas id="c2" width="200" height="60"></canvas>
<!-- 3. Three lines in three colors after one beginPath(). What color is each line?
(Section 2.2.1) -->
<canvas id="c3" width="200" height="60"></canvas>
<!-- 4. One rectangle as the current path and one as a Path2D, both built under
translate(100, 0). Which contains (125, 25) after the reset? (Section 2.2.4) -->
<!-- 5. A shadow offset by 10 under scale(2). Which pixel columns does the visible
shadow cover? (Section 2.3.3) -->
<canvas id="c5" width="200" height="60"></canvas>
<script>
const px = (ctx, x, y) => ctx.getImageData(x, y, 1, 1).data.join(',');
const x1 = c1.getContext('2d');
x1.beginPath(); x1.arc(150, 75, 60, 0, 2 * Math.PI); x1.fill();
console.log('1:', c1.width, c1.height);
const x2 = c2.getContext('2d');
x2.fillStyle = 'red'; x2.fillRect(10, 10, 40, 40);
c2.width = c2.width;
x2.fillRect(60, 10, 40, 40);
console.log('2:', px(x2, 30, 30), '|', px(x2, 80, 30));
const x3 = c3.getContext('2d'); x3.lineWidth = 6; x3.beginPath();
['blue', 'green', 'red'].forEach((color, i) => {
x3.moveTo(10, 10 + i * 20); x3.lineTo(190, 10 + i * 20);
x3.strokeStyle = color; x3.stroke();
});
console.log('3:', [10, 30, 50].map(y => px(x3, 100, y)).join(' | '));
const x4 = document.createElement('canvas').getContext('2d');
x4.translate(100, 0);
x4.beginPath(); x4.rect(0, 0, 50, 50);
const p4 = new Path2D(); p4.rect(0, 0, 50, 50);
x4.resetTransform();
console.log('4:', x4.isPointInPath(125, 25), x4.isPointInPath(p4, 125, 25));
const x5 = c5.getContext('2d');
x5.scale(2, 2); x5.shadowColor = 'black'; x5.shadowOffsetX = 10;
x5.fillStyle = 'orange'; x5.fillRect(10, 5, 20, 20); // covers pixels 20 to 59
const row = x5.getImageData(0, 30, 200, 1).data, dark = [];
for (let x = 0; x < 200; x++) if (row[x * 4 + 3] && !row[x * 4]) dark.push(x);
console.log('5:', dark[0], 'to', dark.at(-1));
</script><!-- 6. A font without a family, and a letter spacing without a unit. What do the two
properties read back? (Sections 2.4.1 and 2.4.3) -->
<!-- 7. drawImage() straight after setting src. Is anything drawn? Is anything thrown?
(Section 2.5.1) -->
<!-- 8. One pixel written with putImageData() and read back. Is it unchanged?
(Section 2.5.2) -->
<!-- 9. willReadFrequently passed on the second getContext() call. Does it apply?
(Section 2.5.2) -->
<!-- 10. The image has loaded, is drawn, and its pixels are read. What happens when
the page is opened from disk? (Section 2.5) -->
<script>
const ctx = document.createElement('canvas').getContext('2d');
ctx.font = '20px'; ctx.letterSpacing = '4';
console.log('6:', ctx.font, '|', ctx.letterSpacing);
const img = new Image(); img.src = 'booknest-logo.png';
try {
ctx.drawImage(img, 0, 0); console.log('7: alpha', ctx.getImageData(5, 5, 1, 1).data[3]);
} catch (e) { console.log('7:', e.name); }
ctx.putImageData(new ImageData(new Uint8ClampedArray([100, 150, 200, 3]), 1, 1), 0, 0);
console.log('8:', ctx.getImageData(0, 0, 1, 1).data.join(','));
const c9 = document.createElement('canvas'); c9.getContext('2d');
const again = c9.getContext('2d', { willReadFrequently: true });
console.log('9:', again.getContextAttributes().willReadFrequently);
img.onload = () => {
const x10 = document.createElement('canvas').getContext('2d'); x10.drawImage(img, 0, 0);
try { console.log('10:', x10.getImageData(0, 0, 1, 1).data.join(',')); }
catch (e) { console.log('10:', e.name); }
};
</script>