Skip to content

Commit

Permalink
deeper CSS reporting
Browse files Browse the repository at this point in the history
  • Loading branch information
martrapp committed Aug 27, 2024
1 parent 79774ea commit 8774263
Show file tree
Hide file tree
Showing 11 changed files with 169 additions and 46 deletions.
5 changes: 5 additions & 0 deletions .changeset/long-llamas-wash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@vtbag/inspection-chamber': patch
---

Improves CCS information on animation panel
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,17 @@ The @vtbag website can be found at https://vtbag.pages.dev/

## !!! News !!!

The current version also includes smaller improvements, moving the Chamber closer to becoming a stable product rather than just a prototype.
You can now observe in real-time how CSS properties are updated by animations and easily see which other properties are applied to the pseudo-elements created by the View Transition API.

The Chamber now also includes improved error reporting for missing animations or corrupted keyframe definitions.

For details, see the [CHANGELOG](https://github.com/vtbag/inspection-chamber/blob/main/CHANGELOG.md)

## What happened so far:

v1.0.5 introduced the ability to switch single animations on and off as you navigate through the timeline of view transitions. For example, you can temporarily disable fades while investigating a morph animation or disable morphing to focus on other features of your transition.
Continuous smaller improvements, moving the Chamber closer to becoming a stable product rather than just a prototype.

Added ability to switch single animations on and off as you navigate through the timeline of view transitions. For example, you can temporarily disable fades while investigating a morph animation or disable morphing to focus on other features of your transition.


v1.0.4 improves opening the Chamber on mobiles, adds an explicit close button to the inner panel and fixes some styling issues.
Expand Down
18 changes: 9 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
},
"devDependencies": {
"@changesets/cli": "^2.27.7",
"@eslint/js": "^9.9.0",
"@eslint/js": "^9.9.1",
"@types/dom-view-transitions": "^1.0.5",
"esbuild": "^0.23.1",
"prettier": "^3.3.3",
Expand Down
101 changes: 82 additions & 19 deletions src/animations.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getNonDefaultPropNames, getNonDefaultProps } from './default-styles';
import { control, namesOfAnimation } from './panel/full-control';
import { plugInPanel } from './panel/inner';
import { getModus } from './panel/modus';
Expand Down Expand Up @@ -85,11 +86,15 @@ export async function retrieveViewTransitionAnimations() {
const endTime = (animation: Animation) =>
(animation.effect?.getComputedTiming().endTime?.valueOf() as number) ?? 0;

inspectionChamber.longestAnimation = animations.reduce((acc, anim) =>
endTime(anim) > endTime(acc) ? anim : acc
);
inspectionChamber.animationEndTime = ~~endTime(inspectionChamber.longestAnimation);

if (animations.length > 0) {
inspectionChamber.longestAnimation = animations.reduce((acc, anim) =>
endTime(anim) > endTime(acc) ? anim : acc
);
inspectionChamber.animationEndTime = ~~endTime(inspectionChamber.longestAnimation);
} else {
inspectionChamber.longestAnimation = undefined;
inspectionChamber.animationEndTime = 0;
}
const oldNames = new Set<string>();
const newNames = new Set<string>();

Expand All @@ -111,11 +116,13 @@ export function unleashAllAnimations() {
});
}

const row = (field: string, value: string) =>
value
? `<tr><td style="text-align:right">${field}</td><td><tt><b>${value}</b></tt></td><tr>`
: '';

export function listAnimations(name: string) {
const row = (field: string, value: string) =>
value
? `<tr><td style="text-align:right">${field}</td><td><tt><b>${value}</b></tt></td><tr>`
: '';
const allProps: Set<string> = new Set();
const meta = ['offset', 'computedOffset', 'easing', 'composite'];
const inspectionChamber = top!.__vtbag.inspectionChamber!;
const styleMap = inspectionChamber.styleMap!;
Expand All @@ -133,19 +140,25 @@ export function listAnimations(name: string) {
const context = JSON.parse(box.dataset.vtbagContext!);
box.removeAttribute('data-vtbag-context');
box.checked = selectAnimation(name, context.pseudo, context.idx)?.playState === 'paused';
box.addEventListener('change', (e) => {
box.addEventListener('change', () => {
if (!stopAndGo(name, context.pseudo, context.idx, box.checked)) {
box.checked = !box.checked;
}
});
});
anim
.querySelectorAll<HTMLDetailsElement>('[data-vtbag-snapshot]')
.forEach((s) => s.addEventListener('toggle', updateSnapshots));
updateLiveValues();
updateSnapshots();
plugInPanel(anim);

function animationPanel(pseudo: string) {
const res: string[] = [];
const style = styleMap.get(`${pseudo}-${name}`) as CSSStyleDeclaration;
const cssAnimation = style?.animation;
const animationName = style?.animationName;
allProps.clear();
if (animationName && animationName !== 'vtbag-twin-noop' && animationName !== 'none') {
const animationNames = animationName.split(', ');
let skipped = 0;
Expand All @@ -156,22 +169,31 @@ export function listAnimations(name: string) {
);
} else {
res.push(
`<span style="padding-right: 0.25ex; width: 4.75ex; display: inline-block; text-align:right">🔴</span> ${pseudo}: <tt>${animationNames[idx]}</tt><br>`
`<span style="padding-right: 0.25ex; width: 4.75ex; display: inline-block; text-align:right">⚠️</span> ${pseudo}: keyframes <tt>${animationNames[idx]}</tt> not found.<br>`
);
++skipped;
console.error(
`[inspection chamber] did not find keyframes named "${animationNames[idx]}" for ::view-transition-${pseudo}(${name})`
);
}
});
if (allProps.size > 0) {
res.push(
`<details data-vtbag-live-values="${pseudo + ',' + [...allProps].sort().join(',')}"><summary>&nbsp;🌀&thinsp; ${pseudo}: live values</summary></details>`
);
}
}

if (style) {
res.push(
`<details data-vtbag-snapshot="${pseudo}"><summary>&nbsp;📸 ${pseudo}: CSS snapshot</summary></details>`
);
}
return res.length > 0 ? res.join('') + '<hr>' : '';

function details(keyframeName: string, animation: string) {
const properties = keyframeProperties(keyframeName) || `⚠️ no properties? `;
return `
<table>
${row('animation:', animation)}
${row('animates:', keyframeProperties(keyframeName))}
${row('animates:', properties)}
${keyframes(keyframeName)}
</table>`;
}
Expand All @@ -182,10 +204,9 @@ export function listAnimations(name: string) {
inspectionChamber.keyframesMap
?.get(name)
?.forEach((k) => Object.keys(k).forEach((key) => keys.add(key)));
return [...keys]
.filter((k) => !meta.includes(k))
.sort()
.join(', ');
const props = [...keys].filter((k) => !meta.includes(k)).sort();
props.forEach((p) => allProps.add(p));
return props.join(', ');
}

function keyframes(name: string) {
Expand Down Expand Up @@ -255,3 +276,45 @@ export function selectAnimation(name: string, pseudo: string, idx: number) {
}
return result;
}

export function updateLiveValues() {
const name =
top!.document.querySelector<HTMLHeadingElement>('h4[data-vtbag-name]')?.dataset.vtbagName;
const chamber = top!.__vtbag.inspectionChamber!;
const styleMap = chamber.styleMap!;
top!.document
.querySelectorAll<HTMLDetailsElement>('[data-vtbag-live-values]')
.forEach((details) => {
const [pseudo, ...props] = details.dataset.vtbagLiveValues!.split(',');
const style = styleMap.get(`${pseudo}-${name}`) as CSSStyleDeclaration;
const values = props.map((p) => row(p + ':', style.getPropertyValue(p)));
details.innerHTML =
`<summary>&nbsp;🌀&thinsp; ${pseudo}: live values</summary><table>` +
values.join('') +
'</table>';
});
}

function updateSnapshots() {
const name =
top!.document.querySelector<HTMLHeadingElement>('h4[data-vtbag-name]')?.dataset.vtbagName;
const chamber = top!.__vtbag.inspectionChamber!;
const styleMap = chamber.styleMap!;
top!.document.querySelectorAll<HTMLDetailsElement>('[data-vtbag-snapshot]').forEach((details) => {
if (details.open) {
const pseudo = details.dataset.vtbagSnapshot;
const live: string[] = (
details.previousElementSibling as HTMLElement
)?.dataset.vtbagLiveValues
?.split(',')
.slice(1) ?? [''];
const style = styleMap.get(`${pseudo}-${name}`) as CSSStyleDeclaration;
const values = getNonDefaultPropNames(style)
.filter((p) => !live.includes(p))
.sort()
.map((p) => row(p + ':', style.getPropertyValue(p)));
details.innerHTML =
`<summary>&nbsp;📸 ${pseudo}: CSS snapshot</summary><table>` + values.join('') + '</table>';
}
});
}
6 changes: 5 additions & 1 deletion src/bench.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<meta name="astro-view-transitions-enabled" content="true" />
</head>
<body>
<div style="display:none" id="vtbot-loading-indicator"></div>
<div style="display: none" id="vtbot-loading-indicator"></div>
<iframe id="vtbag-main-frame" src="/"></iframe>
<div id="divider" class="divider"></div>

Expand Down Expand Up @@ -827,5 +827,9 @@
:is(#vtbag-ui-panel, #vtbag-ui-inner-panel) :is(p, hr) {
margin-bottom: 8px;
}
details[data-vtbag-live-values],
details[data-vtbag-snapshot] {
margin-top: 0.15rem;
}
</style>
</body>
46 changes: 38 additions & 8 deletions src/default-styles.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export const endProps = [];
const allProps: string[] = [];
const redundantProps = ['top', 'left', 'right', 'bottom'];
const basicProps = [
'animation',
'direction',
Expand All @@ -19,24 +20,53 @@ let defaultStyleValues: Record<string, string>;

export function setNonDefaultProps(elemStyle: CSSStyleDeclaration, style: CSSStyleDeclaration) {
defaultStyleValues ??= init();
basicProps.forEach((prop) => {
allProps.forEach((prop) => {
const val = style.getPropertyValue(prop);
if (val !== defaultStyleValues[prop]) {
elemStyle.setProperty(prop, val);
}
});
}

export function getNonDefaultProps(elemStyle: CSSStyleDeclaration) {
defaultStyleValues ??= init();
const res: Record<string, string> = {};
allProps.forEach((prop) => {
const val = elemStyle.getPropertyValue(prop);
if (val !== defaultStyleValues[prop]) {
res[prop] = val;
}
});
return res;
}

export function getNonDefaultPropNames(elemStyle: CSSStyleDeclaration) {
defaultStyleValues ??= init();
const res: string[] = [];
res.push('inset');
allProps.forEach((prop) => {
const val = elemStyle.getPropertyValue(prop);
if (val !== defaultStyleValues[prop]) {
res.push(prop);
}
});
return res.filter(
(prop) =>
!(prop.startsWith('inset-') || prop.startsWith('animation-') || redundantProps.includes(prop))
);
}

function init() {
const defaultDiv = top!.document.createElement('div');
top!.document.body.appendChild(defaultDiv);
const defaultStyles = top!.getComputedStyle(defaultDiv);
const res = basicProps.reduce(
(acc: Record<string, string>, prop: string) => (
(acc[prop] = defaultStyles.getPropertyValue(prop)), acc
),
{}
);
const res: Record<string, string> = {};

for (let i = 0; i < defaultStyles.length; i++) {
const prop = defaultStyles.item(i);
allProps.push(prop);
res[prop] = defaultStyles.getPropertyValue(prop);
}
top!.document.body.removeChild(defaultDiv);
return res;
}
8 changes: 4 additions & 4 deletions src/dragging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ export function initDragging(

let startX: number;
let startY: number;
const lX = (x: number) => x < 0 ? 0 : x > (innerWidth - 16) ? (innerWidth - 16) : x;
const lY = (y: number) => y < 0 ? 0 : y > (innerHeight - 16) ? (innerHeight - 16) : y;
const lX = (x: number) => (x < 0 ? 0 : x > innerWidth - 16 ? innerWidth - 16 : x);
const lY = (y: number) => (y < 0 ? 0 : y > innerHeight - 16 ? innerHeight - 16 : y);
const xe = (e: TouchEvent | MouseEvent, delta = 0) =>
lX(((e instanceof TouchEvent ? e.touches[0]?.clientX : e.clientX) ?? 0)) - delta;
lX((e instanceof TouchEvent ? e.touches[0]?.clientX : e.clientX) ?? 0) - delta;
const ye = (e: TouchEvent | MouseEvent, delta = 0) =>
lY(((e instanceof TouchEvent ? e.touches[0]?.clientY : e.clientY) ?? 0)) - delta;
lY((e instanceof TouchEvent ? e.touches[0]?.clientY : e.clientY) ?? 0) - delta;

const startDragging = (e: TouchEvent | MouseEvent, t: HTMLElement) => {
dragged = t;
Expand Down
2 changes: 2 additions & 0 deletions src/panel/full-control.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { updateLiveValues } from '../animations';
import { getModus } from './modus';
import { showSomeAnimations, THROTTLING_DELAY, updateNameVisibility } from './names';
import { vtActive } from './transition';
Expand Down Expand Up @@ -55,6 +56,7 @@ export function control() {
}
});
}
updateLiveValues();
}

export function namesOfAnimation(animation: Animation) {
Expand Down
17 changes: 16 additions & 1 deletion src/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,30 @@ function frame(namedOnly = false) {
::view-transition-old(*) {
outline: 3px dashed darkslateblue;
border-radius: 5px;
}
[data-vtbag-transition-pseudo="old"] {
outline: 5px dashed darkslateblue;
}
::view-transition-new(*) {
outline: 3px dashed darkolivegreen;
}
[data-vtbag-transition-pseudo="new"] {
outline: 5px dashed darkolivegreen;
}
::view-transition-group(*),
[data-vtbag-transition-name] {
outline: 2px dotted darkgoldenrod;
}
[data-vtbag-transition-pseudo="group"] {
outline: 3px dotted darkgoldenrod;
}
::view-transition-image-pair(*) {
outline: 1px solid #8888;
}
[data-vtbag-transition-pseudo="image-pair"] {
outline: 3px solid #8888;
}
::view-transition-group(*),
::view-transition-image-pair(*),
::view-transition-old(*),
Expand Down
Loading

0 comments on commit 8774263

Please sign in to comment.