{"id":8304,"date":"2026-01-20T12:03:38","date_gmt":"2026-01-20T17:03:38","guid":{"rendered":"https:\/\/frontendmasters.com\/blog\/?p=8304"},"modified":"2026-01-21T11:01:02","modified_gmt":"2026-01-21T16:01:02","slug":"view-transitions-playing-video","status":"publish","type":"post","link":"https:\/\/frontendmasters.com\/blog\/view-transitions-playing-video\/","title":{"rendered":"View Transitions &amp; Playing Video"},"content":{"rendered":"\n<p>I was runnin&#8217; my mouth the other day in a conversation about View Transitions and I believe I said that you can keep audio &amp; video playing during a View Transition. Now that I&#8217;m sitting down to actually prove it, the answer seems to be more nuanced:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Same-Page View Transitions: Just Works\u2122<\/li>\n\n\n\n<li>Multi-Page View Transitions: Doesn&#8217;t Work&#8230; well&#8230; <em>you can fake it though.<\/em><\/li>\n<\/ul>\n\n\n\n<p>Let&#8217;s start with that first one as it&#8217;s easy and satisfying. But first <a href=\"https:\/\/codepen.io\/editor\/chriscoyier\/pen\/019bc461-b5cb-71e3-9ab9-558ebb5ff512\">here&#8217;s both in a demo<\/a>. I would embed the demo here, but I find that multi-page view transitions don&#8217;t behave well in the embed for whatever reason. So rather than you try it and be confused or disappointed, just go directly to that demo link above.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Same-Page View Transitions and Video<\/h2>\n\n\n\n<p>If you have a <code>&lt;video&gt;<\/code> element on the page and you use a <code>document.startViewTransition<\/code> that manipulates it somehow in the callback, it will preserve the state of the video during the transition. If the video is playing, that playing state will be preserved the entire time. Much like <a href=\"https:\/\/frontendmasters.com\/blog\/preserve-state-while-moving-elements-in-the-dom\/\">the newangled <code>.moveBefore()<\/code><\/a>.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">doViewTransition.addEventListener(<span class=\"hljs-string\">\"click\"<\/span>, () =&gt; {\n    <span class=\"hljs-built_in\">document<\/span>.startViewTransition(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n      <span class=\"hljs-keyword\">const<\/span> $video = <span class=\"hljs-built_in\">document<\/span>.querySelector(<span class=\"hljs-string\">\"video\"<\/span>);\n      $video.classList.toggle(<span class=\"hljs-string\">\"fancy\"<\/span>);\n    });\n});<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Basically nothing to it. This works just as well on an <code>&lt;audio&gt;<\/code> or <code>&lt;iframe&gt;<\/code>. <\/p>\n\n\n\n\t\t<figure class=\"wp-block-jetpack-videopress jetpack-videopress-player wp-block-jetpack-videopress--has-max-width\" style=\"max-width: 629px;\" >\n\t\t\t<div class=\"jetpack-videopress-player__wrapper\"> <iframe title=\"VideoPress Video Player\" aria-label='VideoPress Video Player' width='500' height='308' src='https:\/\/videopress.com\/embed\/FoEBZOTY?cover=1&amp;autoPlay=0&amp;controls=1&amp;loop=0&amp;muted=1&amp;persistVolume=0&amp;playsinline=1&amp;preloadContent=metadata&amp;useAverageColor=1&amp;hd=0' frameborder='0' allowfullscreen data-resize-to-parent=\"true\" allow='clipboard-write'><\/iframe><script src='https:\/\/v0.wordpress.com\/js\/next\/videopress-iframe.js?m=1739540970'><\/script><\/div>\n\t\t\t\n\t\t\t\n\t\t<\/figure>\n\t\t\n\n\n<h2 class=\"wp-block-heading\">Multi-Page View Transitions and Video<\/h2>\n\n\n\n<p>The brass tacks here are that when a page unloads and a new page loads, no state at all is maintained. Even if the exact same <code>&lt;video&gt;<\/code> is on the next page, it&#8217;s not going to remember that&#8217;s playing or where you were. <\/p>\n\n\n\n<p>I was just wrong when I was thinking there was some way to get this to work. There are some understandable sources for the confusion, though. If someone happened to be using an framework that provides SPA (single page app) navigations, you might see persisting video just because, well, the page never unloads. Also: Astro is a popular framework that <a href=\"https:\/\/docs.astro.build\/en\/guides\/view-transitions\/#maintaining-state\">specifically implemented persistent transitions for video<\/a>, and does so by essentially forcing an SPA experience with a same-page view transition.<\/p>\n\n\n\n<p><a href=\"https:\/\/github.com\/w3c\/csswg-drafts\/issues\/10620\">This GitHub thread<\/a> is a feature request for multi-page view transitions to be able to keep state and gets into this a little. Bramus notes that this isn&#8217;t a view transitions specific feature, it&#8217;s <a href=\"https:\/\/github.com\/whatwg\/html\/issues\/8538\">a more general need for state-saving through page navigations<\/a>. It also links to <a href=\"https:\/\/view-transitions.chrome.dev\/video\/mpa\/\">this demo<\/a>, which&#8230; makes it work! This is the &#8220;faking it&#8221; I referred to. It doesn&#8217;t prevent the <code>&lt;video&gt;<\/code> from being unloaded and re-loaded, it just keeps the state in <code>sessionStorage<\/code>. So there is a little blip between pages. But hey it&#8217;s pretty close!<\/p>\n\n\n\n<p>Here&#8217;s the important bits&#8230;<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-comment\">\/* Enable Multi-Page View Transitions *\/<\/span>\n<span class=\"hljs-keyword\">@view-transition<\/span> {\n  <span class=\"hljs-selector-tag\">navigation<\/span>: <span class=\"hljs-selector-tag\">auto<\/span>;\n}\n\n<span class=\"hljs-comment\">\/* Ensure video has a unique name shared on both pages *\/<\/span>\n<span class=\"hljs-selector-tag\">video<\/span> {\n  <span class=\"hljs-attribute\">view-transition-name<\/span>: video;\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-comment\">&lt;!-- regular link goes between pages --&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">a<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\".\/another-page.html\"<\/span>&gt;<\/span>\n  Go to Another Page (Multi-Page View Transition)\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">a<\/span>&gt;<\/span>\n\n<span class=\"hljs-comment\">&lt;!-- video exists on both pages --&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">video<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"https:\/\/assets.codepen.io\/3\/mov_bbb.mp4\"<\/span> <span class=\"hljs-attr\">controls<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">video<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-built_in\">window<\/span>.addEventListener(<span class=\"hljs-string\">\"pageswap\"<\/span>, <span class=\"hljs-keyword\">async<\/span> (e) =&gt; {\n  <span class=\"hljs-keyword\">if<\/span> (e.viewTransition) {\n    <span class=\"hljs-comment\">\/\/ page is leaving... save the video state<\/span>\n  }\n});\n\n<span class=\"hljs-built_in\">window<\/span>.addEventListener(<span class=\"hljs-string\">\"pagereveal\"<\/span>, <span class=\"hljs-keyword\">async<\/span> (e) =&gt; {\n  <span class=\"hljs-keyword\">if<\/span> (e.viewTransition) {\n    <span class=\"hljs-comment\">\/\/ page is entering, get video state and restore<\/span>\n  }\n});<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Here&#8217;s a video where I&#8217;m navigating pages in <a href=\"https:\/\/codepen.io\/editor\/chriscoyier\/pen\/019bc461-b5cb-71e3-9ab9-558ebb5ff512\">the demo<\/a> and you can see it working, and I&#8217;ve recorded the system audio along with it so you can hear the &#8220;blip&#8221; happen between pages:<\/p>\n\n\n\n\t\t<figure class=\"wp-block-jetpack-videopress jetpack-videopress-player wp-block-jetpack-videopress--has-max-width\" style=\"max-width: 468px;\" >\n\t\t\t<div class=\"jetpack-videopress-player__wrapper\"> <iframe title=\"VideoPress Video Player\" aria-label='VideoPress Video Player' width='500' height='561' src='https:\/\/videopress.com\/embed\/mlKxMZOh?cover=1&amp;autoPlay=0&amp;controls=1&amp;loop=0&amp;muted=0&amp;persistVolume=1&amp;playsinline=1&amp;preloadContent=metadata&amp;useAverageColor=1&amp;hd=0' frameborder='0' allowfullscreen data-resize-to-parent=\"true\" allow='clipboard-write'><\/iframe><script src='https:\/\/v0.wordpress.com\/js\/next\/videopress-iframe.js?m=1739540970'><\/script><\/div>\n\t\t\t\n\t\t\t\n\t\t<\/figure>\n\t\t\n\n\n<p>Again, <a href=\"https:\/\/codepen.io\/editor\/chriscoyier\/pen\/019bc461-b5cb-71e3-9ab9-558ebb5ff512\">this demo<\/a> is adapted from <a href=\"https:\/\/view-transitions.chrome.dev\/video\/mpa\/detail.html\">this demo from the Chrome gang<\/a>. I&#8217;m posting mine because I was learning about it and playing with it and hope to make this all more findable information. <\/p>\n\n\n\n<p>We&#8217;ve only looked at <code>&lt;video&gt;<\/code> specifically here, but if you were OK with the &#8220;blip&#8221; thing and wanted to do this with, say, a YouTube video that embeds as an <code>&lt;iframe&gt;<\/code>, the techniques would be the same, you&#8217;d just need to dig out the video information with the <a href=\"https:\/\/developers.google.com\/youtube\/iframe_api_reference\">Iframe Player API<\/a> in order to save and retrieve the playing video information. I think that makes pretty fun homework if you ask me. \ud83d\ude09 <\/p>\n","protected":false},"excerpt":{"rendered":"<p>Can you keep a video playing as a view transitions happens? Yes and no. Mostly yes. <\/p>\n","protected":false},"author":1,"featured_media":8324,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[3,193,101],"class_list":["post-8304","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-post","tag-javascript","tag-video","tag-view-transitions"],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/01\/video-view-transitions.jpg?fit=2000%2C1200&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/8304","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/comments?post=8304"}],"version-history":[{"count":13,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/8304\/revisions"}],"predecessor-version":[{"id":8330,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/8304\/revisions\/8330"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media\/8324"}],"wp:attachment":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media?parent=8304"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/categories?post=8304"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/tags?post=8304"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}