Sync CSS animations with HTML5 <video>
(OBS Studio workaround for Obsoleted WebVfx)

Sometimes it is useful to have a CSS animation with a transparent background playing on top of a video. Unfortunately there is no standard mechanism to synchronise the animation with the video. Either the video will start to play and some time later the animation will start, or (more likely) vice versa. Often it doesn't matter that there is no synchronisation, but at other times it does.

This javascript code provides such a mechanism. It does the following:

Introduction

This code was developed to enable CSS animations to be synchronously overlaid on top of a video clip. This used to be possible to some extent in Shotcut using the Text:HTML (Overlay HTML) filter and a javascript library provided on this website. However, the underlying WebVfx framework that Shotcut used to support this had to be abandoned after Shotcut V20.06.

A workaround solution has been found that involves exporting the video clip as a "visually lossless" video file, importing this as a "Video Source" into OBS Studio (obsproject.com) and running the Text:HTML filter as a "Browser Source" overlaying the clip. This however is where the lack of synchronisation causes problems. It is virtually impossible to get the CSS animation (Browser Source) to start at exactly the same instance in time as the video (Video Source). The solution is to include the video as an HTML <video> element inside the HTML, have the elements being animated positioned above the video with a transparent background and develop some javascript code to do the synchronisation.

To get synchronisation with the HTML/CSS:

The simple HTML file below is an example of a title (in the form of an <h1> header) that zooms-in on top of the video, then zooms-out again using CSS animation keyframes. Most of the CSS is designed to centre the title on the screen.

  <html> 
  <!-- =============================================
       SYNCHRONISE CSS ANIMATIONS WITH HTML5 <video>
       =============================================
  
    N.B. The Google Chrome browser refuses to play sound unless the viewer clicks on the webpage.
    Google says this is to stop annoying sounds/music/speech that users don't want to hear.
    Unfortunately this means the javascript on this page cannot start playing the video unless
    the user clicks on the webpage first, or the music is muted (see the <video> element below).
    You can remove the "muted" attribute when running in OBS Studio-->
    <head>                   
      <style>
        body {
          margin          : 0;
          width           : 1280px;      /* Screen width  = Video width  */
          height          :  720px;      /* Screen height = Video height */
          background-color: transparent; /* Not strictly needed as "transparent" is the default */
          display         : flex;        /* Use 'flex' to centre on the screen */
          flex-direction  : row;
          justify-content : center;      /* Centre text  vertically  inside the logo */
          align-items     : center;      /* Centre text horizontally inside the logo */
          font-size       : 5vh;
          color           : red;
        }
        
        h1 {animation: anim-title 6s ease-in-out 1 forwards;}
        @keyframes anim-title {          /* Title zooms-in, then zooms-out */
           0%, 100% {transform: scale(0);}
          45%,  55% {transform: scale(1);}
        }
        
        video {
          position        : absolute;   /* Absolute positioning to fit the whole of the screen */
          left            : 0;
          top             : 0;
          z-index         : -9999;      /* Places the video underneath all the other HTML elements */
        }
      </style>
    </head>
    <body data-countdown="2"> <!-- Remove the "data-countdown" attribute if you don't want a countdown timer -->
    
      <video width="1280" height="720" preload="metadata" playsinline="true" src='Sample.mp4' muted></video>
      
      <h1 class="animate-this">Video Title (animated)</h1>  <!-- Make sure to set this class on the animated element -->
      
      <script src="./sync_animations.js"></script>  <!-- The synchronisation javascript code -->
      
    </body>
  </html>
        

Instructions

First you need to download the Javascript file by clicking (right-clicking or ctrl-clicking) on the Javascript icon () in the table below.

Downloads - click (or right-click) on the relevant Javascript symbol
DownloadDescription
The Javascript code, called "sync_animation.js"

Workflow (assuming an HD video resolution of 1280x720)

  1. Develop the CSS animation standalone in a modern browser, such as Google Chrome or Microsoft Edge;
  2. In Shotcut export the clip to which the animations are to be applied, using a visually lossless export format, such as:
    Preset       : H.264 High Profile
    Interpolation: Hyper/Lanczos (Best)
    Codec        : libx264
    Rate Control : Quality-based VBR
    Quality      : 66% (crf=17)
                
  3. If your video has audio in it you should (if you haven't already done so)go to the AUDIO settings and set the "Desktop Audio" parameter (e.g. to Default)
  4. There are some reports that you may have to also go to the ADVANCED settings ans deselect the SOURCE parameter "Enable Browser Source Hardware Acceleration", though in most cases it is usually fine to have this set.
  5. Add the video (as a <video> element) to the HTML file with the CSS animations, optionally selecting a countdown timer of e.g. 2 seconds and check that the resulting effect is the desired one;
  6. Open OBS Studio and set the following parameters in settings, to ensure a "visually lossless" video is produced:
    Output Recording:
      Recording Quality: Indistinguishable Quality (Large File Size)
      Recording Format : mkv
      Encoder          : Software (X264)
    Video
      Base   (Canvas) Resolution: 1280x720 (Matches the Shotcut clip format)
      Output (Scaled) Resolution: 1290x720 (Matches the Shotcut clip format)
      Common FPS                : 30       (Matches the Shotcut clip format)
                
  7. Add the HTML file as a "Browser Source";
  8. Go to the Browser Source "properties", delete the "Custom CSS" and set the Width: 1280 and Height: 720 (to match the Shotcut video clip format);
  9. Start recording;
  10. In the Browser Source "properties" click on "Refresh cache of current page";
  11. Press OK, wait for the animation to complete then press "Stop Recording";
  12. Import the video created into Shotcut as a clip to replace the original - trim it to size using the "countdown" visuals to decide where the animation starts.

N.B. The Google Chrome browser sometimes refuses to play sound unless the viewer clicks on the webpage. Google says this is to stop annoying sounds/music/speech that users don't want to hear. Unfortunately this means the javascript may not be able to start playing the video unless the user clicks on the webpage first, or unless the music is muted (see the <video> element in the example). This does not appear to be the case with Microsoft Edge, nor with the Chromium Embedded Framework (CEF) that is the browser in OBS Studio. So you can remove the "muted" attribute when running in OBS Studio.