Now that HTML5 provides an almost-standard way to view videos in the browser, we can combine video with SVG/SMIL animated overlays to create many effects that previously had to be rendered and embedded in the video file. This post looks at how to emulate the graphical elements used in Netflix's "House of Cards," which allow viewers to follow text messages without interrupting the action or limiting their perspective to the screen of a phone.

The House of Cards implementation has evolved over time, from bubbles that expand and fade in all at once to bubbles that show the sender typing and expand to accommodate additional lines. For the sake of demonstrating what SVG/SMIL can handle, we'll be emulating the more complex version.

It's a short clip. Click RERUN to watch it again.

Basic HTML5 Video Primer

Formats Supported

HTML5 attempts to make video viewing as standard as viewing a JPEG image, but the browser support is not there yet. For now you have to encode each video in at least two types:

Why not just one format? In my experience, an .mp4 file is generally going to give you better quality video than an .ogg file of the same size, but a number of different companies hold patents on portions of the mp4 format so some browser makers might have to pay to use it. Other browser makers are among the patent holders, and have no reason to support .ogg, which is free.

You'll just have to encode both until the browser makers and patent holders hammer out an agreement, or until the patents expire (by which time they'll be fighting over the licensing details of HTML7's smell-o-vision feature). There's another format, WebM, which is generally supported by the same browsers that support .ogg, but for the sake of getting you up and running quickly these two will cover your needs.

Free Encoding Software

For .mp4, I am partial to Handbrake. It's easy to use, has lots of features and outputs quality video in a tiny file size. I used it to reduce the video above to less than 1MB. Take note of the following options:

  • Format - This should be set to mp4.
  • Average Bitrate - Playing with this setting can help you manage the tradeoff between image quality and file size. Larger numbers will result in a picture that is closer to your source file, but will require more time/bandwidth to download. Smaller numbers will results in a picture with more artifacts, but will require less time/bandwidth to download.
  • Web optimized - Check this box to optimize the file header for streaming.

For .ogg, I use Miro Video Converter. It has a clean interface, but not as many features as Handbrake and basic functions are kind of buried. Open your source file with the app, click the Format button, select Video, select Ogg Theora, then click the green Convert button.

Playing Video in the Browser

  <video autoplay style="position:absolute;top:0px;left:0px;z-index:-100;">
    <source
        src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/154557/oregon6.mp4"
        type="video/mp4">
    <source 
        src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/154557/oregon6.oggtheora.ogv"
        type='video/ogg; codecs="theora, vorbis"'>
</video>

The video tag is pretty simple. Use the autoplay attribute to get it to start on its own, and use absolute positioning to ensure it will be directly beneath your SVG.

Inside the video tag are two source tags. The browser will try them in the order they are listed, and play the first one it supports. Since my mp4 file is much higher quality than my ogg file, I want that to play if it's supported.

Overlaying Message Bubbles

Revealing Text with ClipPaths

All of the text is present at the very beginning of the animation. It's just not visible. We "reveal" it in various ways to create action and immediacy by using clipPaths.

  <clipPath id="clipText1h">
    <rect x="0" y="-8" width="179" height="0">
        <animate begin="callBubbleA.begin-0.3s" dur="0.2s" fill="freeze" attributeName="height" values="0;11;"/>
    </rect>
</clipPath>

The clipPath that reveals the sender's name and timestamp is a rect with a height of 0. By placing an animate tag inside the rect we change the height to 11 so it covers the text. To visualize what this is doing, picture a rubber sheet that is covering your text. A horizontal slit 179 pixels wide has been cut in the sheet, but you can't see through it. That cut is the rect with a height of 0. If you pull one edge of the opening down 11 pixels, you can see the text. Pulling on that opening is what the animate tag does here.

Other clipPaths animate the width attribute of a rect to emulate the appearance of text being typed.

You can read more about how to implement clipPaths in my earlier blog post SVG Patterns, Rotation and Clippaths.

SMIL and Relative Timing

The begin attribute uses one of SMIL's more powerful features. You can set an animation to begin at the same time as another animation, or offset the start time. If you have a lot of different animations whose firing sequence depends on other animations, you don't have to change all of them every time you change one. In this case, we want the name/datestamp to appear 0.3 seconds before the call bubble, announcing that there is an incoming message. callBubbleA is the ID of the callBubble animation, callBubbleA.begin is the start time of the CallBubble animation, and callBubbleA.begin-0.3s tells the browser to fire this animation at that time minus 0.3 seconds.

Animating the Bubble Shape

The bubble is just a filled path with four states. The initial path is:

  M30,71   c0,0,0,0,0,0    h0   c0,0,0,0,0,0      c0,0,0,0,0,0     c0,0,0,0,0,0      v0   c0,0,0,0,0,0     h0    c0,0,0,0,0,0  V71z

That essentially makes it invisible, but sets up the initial positioning and all those zeros act as placeholders for the values we will use in the later states. The various points on the path are animated at key times to change the shape of the bubble.

  <path id="callBubble" opacity="0.4" fill="#FFFFFF" d="M30,71   c0,0,0,0,0,0    h0   c0,0,0,0,0,0      c0,0,0,0,0,0     c0,0,0,0,0,0      v0   c0,0,0,0,0,0     h0    c0,0,0,0,0,0  V71z">
    <animate id="callBubbleA" begin="1s" dur="5s" fill="freeze" attributeName="d"
    keyTimes="0;0.05;0.30;0.35;0.65;0.70;1;"
    values="
    M30,71   c0,0,0,0,0,0    h0   c0,0,0,0,0,0      c0,0,0,0,0,0     c0,0,0,0,0,0      v0   c0,0,0,0,0,0     h0    c0,0,0,0,0,0  V71z;

    M179,42  c 0,6,0,9,-6,9  H43  c-1,0,-2,6,-12,6  c-1,0,2,-1,2,-4  c0,-3,0,-8,0,-11  V43  c0,-6,0,-9,6,-9  h134  c6,0,6,2,6,9  V56z;
    M179,42  c 0,6,0,9,-6,9  H43  c-1,0,-2,6,-12,6  c-1,0,2,-1,2,-4  c0,-3,0,-8,0,-11  V43  c0,-6,0,-9,6,-9  h134  c6,0,6,2,6,9  V56z;

    M179,56  c 0,6,0,9,-6,9  H43  c-1,0,-2,6,-12,6  c-1,0,2,-1,2,-4  c0,-3,0,-8,0,-11  V43  c0,-6,0,-9,6,-9  h134  c6,0,6,2,6,9  V56z;
    M179,56  c 0,6,0,9,-6,9  H43  c-1,0,-2,6,-12,6  c-1,0,2,-1,2,-4  c0,-3,0,-8,0,-11  V43  c0,-6,0,-9,6,-9  h134  c6,0,6,2,6,9  V56z;

    M179,69  c 0,6,0,9,-6,9  H43  c-1,0,-2,6,-12,6  c-1,0,2,-1,2,-4  c0,-3,0,-8,0,-11  V43  c0,-6,0,-9,6,-9  h134  c6,0,6,2,6,9  V56z;
    M179,69  c 0,6,0,9,-6,9  H43  c-1,0,-2,6,-12,6  c-1,0,2,-1,2,-4  c0,-3,0,-8,0,-11  V43  c0,-6,0,-9,6,-9  h134  c6,0,6,2,6,9  V56z;"/>
</path>

The opacity of the path is set to 0.4 so you can still see the video behind it. You may want to adjust this number depending on the contrast between your text and the video behind it.

When we're ready to clear the screen to make room for a new text to appear, the <g> containing the message bubble and its text simply fade to opacity:0.

  <animate id="callGroupFadeout" begin="responseBubbleA.end+2.5s" dur="0.9s" fill="freeze" attributeName="opacity" from="1" to="0">


Notes and Disclaimers

I shot the video in the demo on a road trip up U.S. 101 north of Florence, OR about a mile past the Heceta Head Lighthouse. So no copyright issues there. For the record, I was not texting while driving and the camera was on a tripod buckled into the passenger seat.


4,765 1 13