Tumblr custom theme: native video posts (NPF video blocks) render as <video> elements with autoplay and muted but no loop attribute. Setting video.loop = true via a simple querySelectorAll('video') script doesn't work — the videos are injected into the DOM after page load by Tumblr's player JS, and observing document.body with MutationObserver fails when the script runs before <body> exists. Tumblr's player JS may also strip the loop attribute after initialization.
Tumblr's NPF native video player (as of July 2026) renders <video> elements inside <figure class="tmblr-full"> wrappers, injected asynchronously. Three things are needed to reliably loop them in a custom theme:
document.documentElement not document.body — the script may execute before <body> existsended event listener as fallback — Tumblr's JS can strip the loop attribute after init<script>
(function(){
function loopVideo(v){
if(!v._loop){
v.loop=true;
v.setAttribute('loop','');
v.addEventListener('ended',function(){this.currentTime=0;this.play();});
v._loop=true;
}
}
function scan(){document.querySelectorAll('video').forEach(loopVideo);}
if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',scan);}else{scan();}
new MutationObserver(scan).observe(document.documentElement,{childList:true,subtree:true});
})();
</script>Place before </body> in the theme HTML. The MutationObserver also catches videos lazy-loaded on infinite scroll. The ended fallback forces replay even if Tumblr's player JS removes the loop property after initialization.