Repository navigation
fix: cover an already-loaded image with its block bone (BON-15) #54
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
hunterbecton
merged 2 commits into
main
from
hunter/bon-15-autocss-block-bones-do-not-occlude-an-already-loaded-img
Aug 29, 2026
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@camp.dev/bones": patch | ||
| --- | ||
|
|
||
| Block bones now cover an image that has already loaded. A loaded opaque image paints over its own background, so an `<img>` with a resolved `src` inside a busy region, or one marked `data-bone="block"`, showed the picture instead of a bone. `auto.css` and `bones.css` now set `object-position` on block bones to push the picture out of its content box. The box and its bone background stay in place. Images, canvas, and video without native controls are covered. A loaded `iframe`, `embed`, or `object` still shows its content, because those elements ignore `object-position`. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| import { afterEach, expect, test } from "vite-plus/test"; | ||
| import { page } from "vite-plus/test/browser"; | ||
| import "../../src/css/auto.css"; | ||
| import { parseColor } from "./expect-color.ts"; | ||
|
|
||
| // --------------------------------------------------------------------------- | ||
| // BON-15: a block bone must cover an image that has already loaded. A loaded | ||
| // opaque image paints over its own background-color, so setting the bone | ||
| // color on the img is not enough; the picture itself has to leave the box. | ||
| // Computed style cannot see paint, so these tests screenshot the element and | ||
| // sample its center pixel. | ||
| // --------------------------------------------------------------------------- | ||
|
|
||
| /** A solid red PNG, drawn rather than hand-encoded so it always decodes. */ | ||
| const RED_PNG = (() => { | ||
| const canvas = document.createElement("canvas"); | ||
| canvas.width = 4; | ||
| canvas.height = 4; | ||
| const context = canvas.getContext("2d")!; | ||
| context.fillStyle = "rgb(255, 0, 0)"; | ||
| context.fillRect(0, 0, 4, 4); | ||
| return canvas.toDataURL("image/png"); | ||
| })(); | ||
|
|
||
| const PAGE = "color: rgb(0, 0, 255); background: rgb(255, 255, 255)"; | ||
|
|
||
| afterEach(() => { | ||
| document.body.innerHTML = ""; | ||
| }); | ||
|
|
||
| async function mountImage(html: string): Promise<HTMLImageElement> { | ||
| document.body.insertAdjacentHTML("beforeend", html); | ||
| const img = document.body.querySelector("img")!; | ||
| await img.decode(); | ||
| return img; | ||
| } | ||
|
|
||
| /** RGB of the pixel at the center of `el` as painted. */ | ||
| async function centerPixel(el: HTMLElement): Promise<[number, number, number]> { | ||
| // With save: false the runner returns the base64 string itself rather than | ||
| // writing a file; the `{ path, base64 }` shape is what a saved shot returns. | ||
| const shot: unknown = await page.elementLocator(el).screenshot({ base64: true, save: false }); | ||
| const base64 = typeof shot === "string" ? shot : (shot as { base64: string }).base64; | ||
| const png = new Image(); | ||
| png.src = `data:image/png;base64,${base64}`; | ||
| await png.decode(); | ||
| const canvas = document.createElement("canvas"); | ||
| canvas.width = png.width; | ||
| canvas.height = png.height; | ||
| const context = canvas.getContext("2d")!; | ||
| context.drawImage(png, 0, 0); | ||
| const { data } = context.getImageData( | ||
| Math.floor(png.width / 2), | ||
| Math.floor(png.height / 2), | ||
| 1, | ||
| 1, | ||
| ); | ||
| return [data[0], data[1], data[2]]; | ||
| } | ||
|
|
||
| /** The bone's own color composited over the white page, per channel. */ | ||
| function expectedBonePixel(el: HTMLElement): [number, number, number] { | ||
| const [r, g, b, a] = parseColor(getComputedStyle(el).backgroundColor); | ||
| const over = (channel: number) => Math.round(channel * a + 255 * (1 - a)); | ||
| return [over(r), over(g), over(b)]; | ||
| } | ||
|
|
||
| async function expectBonePixel(img: HTMLImageElement): Promise<void> { | ||
| const actual = await centerPixel(img); | ||
| const expected = expectedBonePixel(img); | ||
| for (let i = 0; i < 3; i++) expect(Math.abs(actual[i] - expected[i])).toBeLessThanOrEqual(2); | ||
| } | ||
|
|
||
| test("control: outside a busy region a loaded image paints its own pixels", async () => { | ||
| const img = await mountImage( | ||
| `<div style="${PAGE}"><img src="${RED_PNG}" width="48" height="48" alt="" /></div>`, | ||
| ); | ||
| expect(await centerPixel(img)).toEqual([255, 0, 0]); | ||
| }); | ||
|
|
||
| test("auto.css: a loaded image inside a busy region paints its bone color", async () => { | ||
| const img = await mountImage( | ||
| `<section aria-busy="true" data-bone-animate="none" style="${PAGE}"><img src="${RED_PNG}" width="48" height="48" alt="" /></section>`, | ||
| ); | ||
| await expectBonePixel(img); | ||
| }); | ||
|
|
||
| test("bones.css: a loaded image marked as a block bone paints its bone color", async () => { | ||
| const img = await mountImage( | ||
| `<div data-bone-animate="none" style="${PAGE}"><img data-bone="block" src="${RED_PNG}" width="48" height="48" alt="" /></div>`, | ||
| ); | ||
| await expectBonePixel(img); | ||
| }); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.