惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Spread Privacy
Spread Privacy
V
Visual Studio Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Recorded Future
Recorded Future
云风的 BLOG
云风的 BLOG
Microsoft Azure Blog
Microsoft Azure Blog
I
InfoQ
Apple Machine Learning Research
Apple Machine Learning Research
MyScale Blog
MyScale Blog
M
MIT News - Artificial intelligence
WordPress大学
WordPress大学
Recent Announcements
Recent Announcements
V
V2EX
The GitHub Blog
The GitHub Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Jina AI
Jina AI
小众软件
小众软件
aimingoo的专栏
aimingoo的专栏
V
Vulnerabilities – Threatpost
C
Check Point Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
AI
AI
宝玉的分享
宝玉的分享
P
Proofpoint News Feed
量子位
Attack and Defense Labs
Attack and Defense Labs
H
Hackread – Cybersecurity News, Data Breaches, AI and More
P
Privacy International News Feed
Google DeepMind News
Google DeepMind News
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
C
CERT Recently Published Vulnerability Notes
腾讯CDC
Latest news
Latest news
Google DeepMind News
Google DeepMind News
The Register - Security
The Register - Security
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
G
GRAHAM CLULEY
Blog — PlanetScale
Blog — PlanetScale
博客园_首页
美团技术团队
The Cloudflare Blog
T
Tenable Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
J
Java Code Geeks
SecWiki News
SecWiki News
Webroot Blog
Webroot Blog
N
News | PayPal Newsroom
博客园 - 叶小钗
博客园 - Franky

Ben Frain

So, you want a React modal that uses the <dialog> element and transitions in AND out? Scroll indicators on tables with background colours using animation-timeline Review: SoundPEATS Clip1 Open ear clip-on headphones VS Code – highlight just the active indent guide Review: MoErgo Go60, a split ergonomic and fully programmable keyboard Review: Kinesis mWave mechanical ergonomic and programmable keyboard iOS26 Safari theme-color/tab-tinting with fixed position elements is a mess New Book: Responsive Web Design with HTML5 and CSS, 5th Edition Use @supports with a proxy feature/value for features you can’t test for (@starting-style) Review: Benq Screenbar Pro and Halo lightbars. The kit you never knew you needed! Center items in a container, and make then left aligned when they overflow A single element CSS donut timer/countdown timer, that can sit on any background Review: Open Ear Headphones – Bose Open Ultra v Huawei FreeClip In search of the perfect autocomplete for CSS Managing multiple versions of node, without NVM or additional tools Review: Keychron Q14 Max Alice 96 Key mechanical keyboard NEW VIDEO COURSE: Responsive Web Design with HTML5 and CSS Is CSS Grid really slower than Flexbox? Review: Advantage360 Pro Signature Edition 2024 mechanical ergonomic keyboard More Keys or Fewer Keys for mechanical keyboards Yes! You can use position: sticky and overflow together Neovim – how to do project-wide find and replace? Review: Keyboardio Model 100, split, wooden, mechanical keyboard Struggling to learn SwiftUI How to create rounded gradient borders with any background in CSS How to get equal size icons in the cmp completion menu of Neovim with Kitty terminal Review: Dygma Defy, split, mechanical, programmable ergonomic keyboard What’s the best way to reset WAAPI chained animations? Using CSS @property inside shadowRoot (web components) workaround Dynamically create a ref for items when iterating over them in lit.dev templates Selecting and pausing running animations in Lit Web Components New Web APIs — a popover on top of a dialog element can’t be interacted with? Review: ZSA Voyager, split, mechanical keyboard Russel Brand, narcissism, and a sadly common pattern… When it comes to text editors, I feel like Goldilocks Simple settings for writing and converting markdown with Sublime Text Review: The ZSA Platform tenting kit for the Moonlander keyboard Logitech MX Master 3/3s scroll wheel fix Building a line graph with CSS clip-mask Review: Dell 6K 32″ Monitor U3224KBA I broke my keyboard! Swapping the key switches in the Kinesis Advantage360 Pro HUGE macOS Productivity boost: Set-up simple, keyboard only, instant App switching and arrangement Adding to $PATH for a central location for Neovim/NPM tools Neovim Power Tips: Volume 2 Review: MoErgo Glove80, split, wireless, columnar ergonomic keyboard with RGB Review: Kinesis Advantage 360 Pro — split ergo mechanical keyboard Review: Dactyl Manuform – an ergonomic, custom built mechanical keyboard How to animate along an SVG path at the same time the path animates? Getting the context of Web Components (lit)
First adventures in View Transitions
Ben Frain · 2025-05-13 · via Ben Frain

Until recently I had taken only the most cursory look at View Transitions. Now they are in Safari too, and therefore viable across multiple platforms, I wanted to understand them a little more and start making use of them in production.

I imagine I am where most working front-end devs are at this point; aware of View Transitions, but I, and no-one I knew personally, was using them day to day.

If you don’t know what View Transitions do conceptually, here’s the overview: they allow you to transition from one view; which is how things are currently on the page, to the new view; how things are going to be after you have clicked a button (or performed any other action that would change the DOM in some way). This magic trick is performed by the browser taking an image of the before state, an image of the new state, and transitioning between the two. They have been supported in Safari since 18.0 and in Chrome since 111.

Same-document, not cross-document first

Same-document View Transitions, that operate on changes in a single page, as opposed Cross Document View Transitions, which operate when you change pages, are what we are looking at here.

Same-document, or single-document View Transitions as they are also known, require some kind of DOM change to kick off. But that can be as straightforward at toggling a class or attribute. So we need some JavaScript to initiate a View Transition. A basic setup could look something like this:

section.addEventListener("click", handleClick);
function handleClick(this) {
    document.startViewTransition(() => {
        const sectionClicked = this;
        sectionClicked.classList.toggle("active");
    });
}

We have sections on our page. When clicked, they fire the handleClick function, and in that we start the View Transition.

The thing to concentrate in that snippet of JavaScript is the startViewTransition part. What goes inside that function gets run inside a ‘default’ view transition. The default transition fades the old ‘image’ to the new. It does that primarily with opacity, going from 0 to 1 and vice versa but also includes some fine detail such as mix-blend-mode: plus-lighter;. It would be remiss of me at this point to not link up the main source of truth on View Transitions on the Chrome site: https://developer.chrome.com/docs/web-platform/view-transitions/same-document

To test View Transitions for ourselves, we won’t be updating the DOM, content wise. Instead we can just toggle a class or two to enable us to move content around with CSS. That’s good enough for View Transitions to do their thing, and means we don’t need to worry about a framework like React or Lit etc at this point. So, yes, to use View Transitions, in their simplest form, you can just use HTML, CSS and with just a little JavaScript.

The task here is to create a simple layout of card-like items. The idea being that you can click on any one of the items, to have it transition into a sort of lightbox modal in the center of the viewport.

So, this would be the initial state:

The start layout for the transition, a grid of 8 cards
All items in document flow, prior to one being clicked

And this would be the new state when a card is clicked:

One card in a modal position
When clicked we transition the card out of the document flow

The only thing that is actually changing in the DOM are some classes that are being toggled. What you can’t easily see from an image is that when a section is made ‘active’ with a class change, it is made absolutely positioned, so out of the document flow, meaning the other, underlying items need to flow back into position in the background as they remain in document flow.

Now, before we go any further, I want to highlight what I feel is the most important thing at the outset. EVERY element that you want to take place in a View Transition needs its own name. In a moment, you will see that we use the CSS view-transition-name: property. Even if you have 8 sections, like the cards in this example, that all look the same, and you want them to do the same thing, such as transition to their existing position to their new one, they cannot all have the same view-transition-name property because they are all different elements, that live in a different position. Just to nail this down – if you do this – nothing will work because you are giving multiple items in the DOM the same view-transition-name:

/* This will break everything */
.section {
    view-transition-name: section;
}

Instead you will need to give each of the alike items an individual name, just as you might add an individual id for example.

In this example, I have to find each of the sections in the DOM to add a click event listener, so I used that opportunity to add a view transition name via a Custom Property:

allSections.map((section, idx) => {
    const thisHeader = section.querySelector(".name");
    section.addEventListener("click", handleClick);
    section.style.setProperty(`--viewTransitionName`, `name-${idx}`);
});

And I can add that to each sections CSS like this, as each section is going to get its own local CSS Custom Property like section-1, section-2 etc

.section {
    view-transition-name: var(--viewTransitionName);
}

And with that I get an OK looking transition when run quickly. But my brain is telling me something is not quite right.

What about z-index

Like all animation issues, you often need to slow them right down to see what is happening. This is where either getting comfortable with the animation tab of the developer tools helps, or the more heavy-handed approach of adding a very long transition time to the default view transition, which we can do with these pseudo-classes.

::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
    animation-duration: 5s !important;
}

Currently, when you click a section, if it isn’t the last section in the DOM, you can see it kind of jumps from behind the others to in-front as it transitions to its new spot. This is because of the original paint order of that element when clicked. There is a more thorough background on this here: https://github.com/w3c/csswg-drafts/issues/8941 where a Chrome developer explains the rationale. Here is the most relevant section of how things are handled:

First paint all the view-transition-names in the old DOM, in the order in which they are painted in the old DOM. Then paint all the view-transition-names which are only in the new DOM, in the order in which they are painted in the new DOM.

I decided to tackle this by first applying a low z-index to each of the sections, and then applying a high z-index on just the item that was clicked. I’m doing that outside of the startViewTransition because otherwise the screenshot would already be captured with the items in the wrong/original stacking order.

function handleClick(this) {
    allSections.map((section) => (section.style.zIndex = "1"));
    this.style.zIndex = "100";

    document.startViewTransition(() => {
        const sectionClicked = this;
        sectionClicked.classList.toggle("active");
    });
}

So that’s the z-index sorted, but still things aren’t as I would like. The text from old and new doesn’t line up, it kind of flies off at a daft size. At this point, the section has its own view-transition-name, and the title of each card has a view-transition-name, the other internal items in the card don’t. So in my mind, that’s because each view-transition-name is like taking a photo of just the element it applies to, and allows a personal old and new transition. Because the shape of the main section is changing, and the various text elements just lives in that without their own view-transition-names, they gets zoomed between the old and new size of the box.

At least I think that is the reason, so I will add individual view-transition-name to each element.

As I have 8 cards, and there are elements for names, manufacturers, connectivity, switches and keys, which all need to move to the correct place, that’s a lot (64!) of individual view-transition-names to add. As I already have my loop for the sections in JavaScript, I am now adding this for each so I only have to add 8 and the loop takes care of each instance:

section.style.setProperty(`--viewTransitionName`, `section-${idx}`);
name!.style!.setProperty(`--viewTransitionName`, `titleName-${idx}`);
connectivity.style.setProperty(
    `--viewTransitionName`,
    `connectivity-${idx}`
);
switches.style.setProperty(`--viewTransitionName`, `switches-${idx}`);
keys.style.setProperty(`--viewTransitionName`, `keys-${idx}`);
image.style.setProperty(`--viewTransitionName`, `image-${idx}`);
manufacturer.style.setProperty(
    `--viewTransitionName`,
    `manufacturer-${idx}`
);
notes.style.setProperty(`--viewTransitionName`, `notes-${idx}`);

That improves things significantly. You can see what that’s like here: https://codepen.io/benfrain/pen/vEEpQPg

Transitioning from display: none can still be problematic

If transitioning from things that are not in the DOM, to things that are, and vice versa, it is important to consider the visual effect this will have. In this example, the .notes section is set to display: none; by default, and display: block; when the card is active. However, as the initial state is that the notes are not in the DOM, the view transition only has the new position to transition to. This means instead of the notes appearing to move from their original position, they appear in their new position in the center and merely fade in, giving the effect that they were never actually part of the card.

At this point, if I speed up the animation speed to only .25s it’s pretty hard to see that shortcoming, but that it exists bugs me.

I briefly experimented with using @starting-style in attempt to get the text to seem to come from the same origin point, but to no avail. Is there a smarter way to handle this?

Aspect Ratio changes

Besides the issue of transitioning things that are not currently showing in an element, we still have the issue of what looks like the outer box of each section growing to a weird shape, while another version of the box grows to the correct shape.

Thankfully people have already figured this out. Related to our earlier text issue, this is down to aspect ratio changes between the old snapshot, and the new. The absolute best explainer for what’s going on here is https://jakearchibald.com/2024/view-transitions-handling-aspect-ratio-changes/

The relevant section being:

The default transition animates the ::view-transition-group from its old size and position to its new size and position. The views, the ::view-transition-old and ::view-transition-new, are absolutely positioned within the group. They match the group’s width, but otherwise maintain their aspect ratios.

So, in this instance it is matching the original aspect ratio. And to fix it, we need to tell that set of transitions to have a height of 100% also. We have 8 of these boxes, with their view-transition-name generated dynamically, so rather than write out all 8 lets test the theory with one in the CSS.

::view-transition-old(section-3),
::view-transition-new(section-3) {
    height: 100%;
}

With that in place, test out the difference at https://codepen.io/benfrain/pen/VYYQRGY by clicking on the ‘Corne’ card, and then comparing that box as it grows/shrinks with any of the others.

With 8 cards on the page, that will mean either writing them all out like our test one above for each section, or adding them with JavaScript. I opted for the later. First getting the documents style sheet, and then adding a rule in the loop for each section:

const ss = document.styleSheets[0];

// Inside the loop of all sections
ss.insertRule(
    `::view-transition-old(section-${idx}),::view-transition-new(section-${idx}) {height: 100%;}`
);

With that in place the ‘boxes’ of each card/section animate better, and look even better at speed (play with the animation-duration at the top of the styles to see). You can play with that here: https://codepen.io/benfrain/pen/gbbvyOW

At this point, things are looking pretty much where I wanted to get them as a first venture into View Transitions, there just seems to be far more code than I would have liked.

Maybe I have just approached and written this inefficiently? That’s definitely a distinct possibility. Plus there are extra niceties being added to View Transitions like https://www.w3.org/TR/css-view-transitions-2/#layered-capture-overview which might fix the complexities getting things like aspect ratio changes working as I would prefer.

Can we wrap alike elements to reduce the number of view-transition-name items needed?

In the hopes of reducing the amount of JavaScript being used, I wondered if altering the DOM a little to wrap those text elements in a common parent, and give that a view-transition-name instead of each individual one might work?

And, it kind of does, but then as some of those cards have text that causes that surrounding box to change shape between old and new, there is some ‘ghosting’ of the text items as it goes from old to new. At this point, I’m feeling like the view-transition-name almost works like a ‘detail’ control. The more individual items that can have them, the finer the transition turns out to be.

You can try a version with those top text elements wrapped in the DOM here: https://codepen.io/benfrain/pen/XJJZvKL. That approach means a little extra HTML, but lets you remove the 16 commented JS lines out of this snippet at the expense of cards like the ‘Defy’ one not transitioning quite so cleanly:

allSections.map((section, idx) => {
    const notes = section.querySelector(".notes");
    const textItems = section.querySelector(".text-items");
    // const name = section.querySelector(".name");
    // const connectivity = section.querySelector(".connectivity");
    // const switches = section.querySelector(".switches");
    // const keys = section.querySelector(".keys");
    const image = section.querySelector(".image");
    // const manufacturer = section.querySelector(".manufacturer");
    section.addEventListener("click", handleClick);
    section.style.setProperty(`--viewTransitionName`, `section-${idx}`);
    // name!.style!.setProperty(`--viewTransitionName`, `titleName-${idx}`);
    // connectivity.style.setProperty(
    //     `--viewTransitionName`,
    //     `connectivity-${idx}`
    // );
    // switches.style.setProperty(`--viewTransitionName`, `switches-${idx}`);
    // keys.style.setProperty(`--viewTransitionName`, `keys-${idx}`);
    image.style.setProperty(`--viewTransitionName`, `image-${idx}`);
    textItems.style.setProperty(`--viewTransitionName`, `textItems-${idx}`);
    // manufacturer.style.setProperty(
    //     `--viewTransitionName`,
    //     `manufacturer-${idx}`
    // );
    notes.style.setProperty(`--viewTransitionName`, `notes-${idx}`);
    ss.insertRule(
        `::view-transition-old(section-${idx}),::view-transition-new(section-${idx}) {height: 100%;}`
    );
});

For me it wasn’t worth it, so I reverted to where I was previously. I’ll likely just tidy the content up, throw in some proper images and call that test finished. You can view where I ended up here:

See the Pen
View Transition Stage 6
by Ben Frain (@benfrain)
on CodePen.

View Transitions are not a panacea

The more you explore and implement view transitions, the more likely you are to bump up against some of the frustrating edge cases. The default animation gives a nice transition for free, but as soon as you start to try and get more bespoke effects, there is quite a bit of work to be done.

If your elements are the same aspect ratio in old and new states, view transitions are a fantastic candidate. If they are not, expect to do a bunch of extra work trying to get an effect you are happy with.

Many of the visual shortcomings, even with aspect ratio changes can be given a pass if you keep speeds nice and snappy. Like a good magician, make sure any tricks are performed before the audience has realised what just happened.

I’m happy view transitions exist but its important to note that while the default is simple and effective, there are new concepts to understand well if you want to wield them with confidence.

As I personally use them more, I expect to look back on this in time, and chuckle at how little I understood at this point.

As ever, I welcome anyone schooling me on what I may have done wrong here, or could be improved in my approaches.

Level 2 of the View Transitions specification details a bunch of additions and improvements. Besides cross-document view transitions, Nested view transitions help with the clipping of elements as they transition. Also Selective view transitions introduce the ability to deal only with active view transitions. More fundamental and useful for me, it also introduces the concept of view-transition-name: auto; which would automatically generate a unique value for every instance of when a new view-transition-name is needed. That will save a lot of the repetition in the CSS! Finally, although seemingly not fully fleshed out, its worth mentioning Layered capture. If I understand it correctly, it means that we will get fundamentally better and finer-detailed transitions between states, without needing to do extra work as properties like clip-path, filter, border-radius and more will be factored into transitions.