CSS 3 Rotating Billboard

This was a fun little exercise trying to replicate those rotating billboards you see on the freeways or even in some baseball parks behind the batters' box. Basically, the idea was to create a row of 3D triangular slats which can be rotated together to show a different image / ad / whatever. And, like most things I've been posting here lately, this doesn't really play well with IE or any older browsers.

So the first thing to solve was how to make this shape:

*note: I added some extra rotation so to this so we get the full 3D effect.

Let's break this down, the html is pretty simple, it's a container that is the single rotating slat, and the child elements that represent the individual faces. I also added an extra element around all of that in order to keep the css perspective property in-check. I'm normally not one to add gratuitous elements, but it seemed to work best here.

The html for an individual triangle then looks like this:

<div class="slat-outer">
	<div class="slat">
		<div class="face"></div>
		<div class='face'></div>
		<div class='face'></div>
	</div>
</div>

Once we have that, the rest is pretty straightforward. We just need to find suitable images for each of the 3 faces, put the collection of slats inside of a container element, and write some javascript to get them to rotate at a given interval.

Here's the finished product:

View Demo

The final HTML, then looks like this:

<div class="billboard">
	<div class="slat-outer">
		<div class="slat">
			<div class="face inview"></div>
			<div class='face'></div>
			<div class='face'></div>
		</div>
	</div>
	<div class="slat-outer">
		<div class="slat">
			<div class="face inview"></div>
			<div class='face'></div>
			<div class='face'></div>
		</div>
	</div>
	<div class="slat-outer">
		<div class="slat">
			<div class="face inview"></div>
			<div class='face'></div>
			<div class='face'></div>
		</div>
	</div>
	<div class="slat-outer">
		<div class="slat">
			<div class="face inview"></div>
			<div class='face'></div>
			<div class='face'></div>
		</div>
	</div>
	<div class="slat-outer">
		<div class="slat">
			<div class="face inview"></div>
			<div class='face'></div>
			<div class='face'></div>
		</div>
	</div>
	<div class="slat-outer">
		<div class="slat">
			<div class="face inview"></div>
			<div class='face'></div>
			<div class='face'></div>
		</div>
	</div>
	<div class="slat-outer">
		<div class="slat">
			<div class="face inview"></div>
			<div class='face'></div>
			<div class='face'></div>
		</div>
	</div>
	<div class="slat-outer">
		<div class="slat">
			<div class="face inview"></div>
			<div class='face'></div>
			<div class='face'></div>
		</div>
	</div>
</div>

And the CSS looks like this:

.billboard {
  display: -webkit-box;
  display: -moz-box;
  display: box;
  margin: 20px auto;
  width: 270px;
  border: 15px solid #ccc;
  background-color: #333;
  -webkit-box-shadow: 3px 2px 5px rgba(0, 0, 0, 0.5);
  -moz-box-shadow: 3px 2px 5px rgba(0, 0, 0, 0.5);
  box-shadow: 3px 2px 5px rgba(0, 0, 0, 0.5);
}

.slat-outer {
  position: relative;
  z-index: 1;
  width: 30px;
  height: 409px;
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  box-sizing: border-box;
  -webkit-perspective: 1000px;
  -moz-perspective: 1000px;
  perspective: 1000px;
  -webkit-perspective-origin: 50% 50%;
  -moz-perspective-origin: 50% 50%;
  perspective-origin: 50% 50%;
}

.slat-outer:first-child {
  border-left: none;
}

.slat {
  width: 30px;
  height: 409px;
  position: absolute;
  z-index: 1;
  -webkit-transform-style: preserve-3d;
  -moz-transform-style: preserve-3d;
  transform-style: preserve-3d;
  -webkit-transition: -webkit-transform 1s linear;
  -moz-transition: -moz-transform 1s linear;
  transition: transform 1s linear;
  -webkit-backface-visibility: hidden;
  -moz-backface-visibility: hidden;
  backface-visibility: hidden;
}

.slat .face {
  position: absolute;
  top: 0;
  left: 0;
  width: 30px;
  height: 409px;
  -webkit-backface-visibility: hidden;
  -moz-backface-visibility: hidden;
  backface-visibility: hidden;
  -webkit-box-shadow: inset 20px 0 30px rgba(0, 0, 0, 0.3);
  -moz-box-shadow: inset 20px 0 30px rgba(0, 0, 0, 0.3);
  box-shadow: inset 20px 0 30px rgba(0, 0, 0, 0.3);
  -webkit-transition: all 0.5s 0s linear;
  -moz-transition: all 0.5s 0s linear;
  transition: all 0.5s 0s linear;
}

.slat .face.inview {
  -webkit-box-shadow: none;
  -moz-box-shadow: none;
  box-shadow: none;
  -webkit-transition-delay: 0.5s;
  -moz-transition-delay: 0.5s;
  transition-delay: 0.5s;
}

.slat .face:nth-child(1) {
  background-image: url(../img/guinness-1.jpg);
  color: #fff;
  -webkit-transform: translateZ(9px);
  -moz-transform: translateZ(9px);
  transform: translateZ(9px);
}

.slat .face:nth-child(2) {
  background-image: url(../img/guinness-2.jpg);
  color: #000;
  -webkit-transform: rotateY(-120deg) translateZ(9px);
  -moz-transform: rotateY(-120deg) translateZ(9px);
  transform: rotateY(-120deg) translateZ(9px);
}

.slat .face:nth-child(3) {
  background-image: url(../img/guinness-3.jpg);
  color: #fff;
  -webkit-transform: rotateY(120deg) translateZ(9px);
  -moz-transform: rotateY(120deg) translateZ(9px);
  transform: rotateY(120deg) translateZ(9px);
}

.slat-outer:nth-child(1) .face {
  background-position: -0px 0;
}

.slat-outer:nth-child(2) .face {
  background-position: -30px 0;
}

.slat-outer:nth-child(3) .face {
  background-position: -60px 0;
}

.slat-outer:nth-child(4) .face {
  background-position: -90px 0;
}

.slat-outer:nth-child(5) .face {
  background-position: -120px 0;
}

.slat-outer:nth-child(6) .face {
  background-position: -150px 0;
}

.slat-outer:nth-child(7) .face {
  background-position: -180px 0;
}

.slat-outer:nth-child(8) .face {
  background-position: -210px 0;
}

.slat-outer:nth-child(9) .face {
  background-position: -240px 0;
}


And finally, a little bit of javascript to handle the rotation of the slats. There's a little more here than just tracking and applying the rotation. After my first pass at this, I wasn't really feeling the effect because everything looked so flat. So I added a little more to the script to apply a className "inview" to the faces currently in-view so that we could apply a box shadow to those no currently visible. Adding the box shadow definitely helped give the billboard a better sense of depth and seemed to bring this home a little better.

;(function () {
	var Billboard, BillboardController, 
        getCSS3Property = (function () {
        	var prefixes = ['Moz', 'webkit', 'o', 'Ms'], 
        	    div = document.createElement('div');

        	return function (propertyName) {
        		var i, len;
	        	for (i = 0, len = prefixes.length; i < len; i ++) {	
	        		if (typeof div.style[prefixes[i] + propertyName] !== 'undefined') {
	        			return prefixes[i] + propertyName;
	        		}
	        	}
	        }

        } )();

	BillboardController = (function () {
		return {
			init: function (billboardElement) {
				var self = this;
				this.billboard = new Billboard(billboardElement);
				this.interval = setInterval(function () {
					self.billboard.rotate();
				}, 5000);
			},
		};
	})();

	Billboard = function (container) {
		this.container = container;
		this.slats = container.querySelectorAll('.slat');
		this.faceInView = 1;
		this.currRotation = 0;
	};

	Billboard.prototype = {
		sides: 3,
		singleRotation: 120,
		
		rotate: function () {
			var i, len;
			this.currRotation += this.singleRotation;

			this.faceInView = this.faceInView + 1 <= 3 ? this.faceInView + 1 : 1;

			for (i = 0, len = this.slats.length; i < len; i++) {
				this.slats[i].style[getCSS3Property('Transform')] = 'rotateY(' + this.currRotation + 'deg)';
			}
		},

		set currRotation (rotation) {
			this._currRotation = rotation;
		},

		get currRotation () {
			return this._currRotation;
		},

		set faceInView (face) {
			var i, len,
			    inview = this.container.querySelectorAll('.face.inview'),
			    nextInView = this.container.querySelectorAll('.face:nth-child(' + face + ')');

			this._faceInView = parseInt(face, 10);

			for (i = 0, len = inview.length; i < len; i++) {
				inview[i].classList.remove('inview');
			}

			for (i = 0, len = nextInView.length; i < len; i++) {
				nextInView[i].classList.add('inview');
			}
		},

		get faceInView () {
			return parseInt(this._faceInView, 10);
		}	
	};

	window.addEventListener('DOMContentLoaded', function () {
		var billboards = document.querySelectorAll('.billboard'),
		    i, len; 

		for (i = 0, len = billboards.length; i < len; i ++) {
			BillboardController.init(billboards[i]);
		}
		
	});

})();

That's it—and, of course like so many things I've been doing here as of late, this only works in browsers that support 3D transforms.

View Demo

Comments

orlando location
Make sure you kitchen cabinets know the color that will surely impress your guests as well as decorative wood knot along with other elements of the particular huge rates and also charges. And even if you decide on kitchen cabinets a colored handle? Plus there is a pretty good bow in it, which is very durable, sticks easily and is washable. You have to be made from a better material.

Add new comment