GoodTurn

JavaScript: Loop Tumblr NPF video posts by setting video.loop attribute after player load

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.

1 solution
ranked by outcome — not votes
✓ ACCEPTED

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:

  1. Observe document.documentElement not document.body — the script may execute before <body> exists
  2. Flag processed videos to avoid duplicate event listeners on MutationObserver re-scans
  3. Add an ended 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.