Some web developers assume that HTML5 video is somehow sacrosanct from manipulation by . As you can see by moving your mouse over the header above and clicking on the revealed images, that’s not true in the slightest.

Inspired by the closing credits of Monsters University, which uses an animated theme of “Scarer Trading Cards”, this small piece has very simple markup, using videos kindly supplied by photographer Alexander Wagner:

<div id="scatter">
	<figure id="desk">
		<a href="#" id="estee"> 
			<video poster="estee.png">
				<source src="estee.mp4" type="video/mpeg">
				<source src="estee.webm" type="video/webm">
		<a href="#" id="indre"> 
			<video poster="indre.png">
				<source src="indre.mp4" type="video/mpeg">
				<source src="indre.webm" type="video/webm">

The outer <div> supplies the 3D perspective, while the <figure> element within creates the plane for the videos. Each video has two sources for cross-browser compatibility, and a poster frame to act as a stand in before the video is played.

The CSS controlling this is slightly more complex. (Vendor prefixes have been removed to simplify the code)

figure#desk {
	transform: rotateX(60deg);
	position: relative;
	width: 200%;
	height: 900px;
	background: url(woodgrain.png); 
	margin-left: -150px;
	margin-top: -150px;
	transition: 1.2s 2s; 
div#scatter {
	perspective: 700px;
	width: 100%;
	height: 500px;
	overflow: hidden; 
	background: linear-gradient(#6c6769  0%, #0a0a0a 100%); 
figure#desk a  { 
	position: absolute;
	top: 150px;
	border: 2vw solid #ffe;  
	box-shadow: 0px 0px 6px 6px rgba(0,0,0,0.3); 
	width: 25%;
	font-size: 0;
	background: #000; 
figure#desk a video { 
	width: 100%;
	font-size: 0;
	transition: .6s; 
a#estee  {
	transform: translateX(200%) rotateZ(60deg);
	transition: 2s; 
figure#desk:hover a#estee  {
	transform: translateX(30%) rotateZ(700deg); 
a#indre {
	top: 300px;
	transform: translateX(220%) rotateZ(48deg);
	transition: 2s .4s; 
figure#desk:hover a#indre {
	transform: translateX(100%) rotateZ(360deg); 
figure#desk:hover {
	transform: rotateX(10deg);
figure#desk a:hover video {
	opacity: 0.5;
figure a:hover:before {
	opacity: 1;
figure#desk a:before { 
	content: "play";
	font-size: 30px;
	position: absolute;
	top: 45%; left: 45%;
	color: #fff;
	opacity: 0;
	transition: .3s; 

The videos are brought onscreen by using rotate and translate, triggered by hovering over the containing <figure> element. Each video is surrounded by a link, which provides generated content for the “play” prompt, and fades the video on hover. (The <video> elements themselves can’t create generated content due to the fact that they are replaced elements).

There are no playback controls, so we must start the videos with a click on their parent elements via some JavaScript:

	var desk = document.getElementById('desk'),
	links = desk.getElementsByTagName('a');
	for ( var i=0;i<links.length;i++ ){
	function handler(e){
		var x =; 
		var video = x.querySelector("video");;

When a video is playing, we don’t want the “play” prompt to show, so we use CSS that works off the class we just applied with JavaScript:

a.playing:hover:before {
	content: none;
	opacity: 0;
a.playing video { opacity: 1; }

There are many potential improvements that could be made here: the videos should be flagged with a “playable” status only when they are fully on-screen, and reset after they have reached their final frame. Arguably, the entire animation would be better predicated on keyframes, rather than transitions. But, crude as it is, I hope that it might inspire more work in CSS 3D manipulation of HTML5 video.

Enjoy this piece? I invite you to follow me at to learn more.
Check out the CodePen demo for this article at