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
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
Ogg Theora, then click the green
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>
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.
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
<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>
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.
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
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.