How to make the Apple TV 3D Button

If you pay close attention to user interfaces you may have noticed that the primary menu and movie/tv cover art on the new and soon to be released (in october) apple tv are three dimensional.

touch_remote_large_2xThe practical purpose of which is that it helps you sense where your position in the swipe controlled user interface resides. However it is also easy to see that it provides a unique wow factor to the move poster art we are all used to seeing in 2D.

Upon seeing this I thought there could be a practical purpose to its implementation on the web by helping the user track their cursor position as well as create some visual intrigue with the highlighted content it could be marketing.

Tada! I’m done!

See the Pen 3D Panel Button by Al Nemec (@althe3rd) on CodePen.

Ok so maybe I should explain a bit of this. To sum it up…  I created a primary container that I could set CSS properties to preserve 3d and set its perspective size.  From there I created three containers contained within that have varying Z positions to create the background, middle, and foreground levels.

The complexity came into play when I then needed to create the script that would change the angle of the primary container based on the cursors position only while it was within the container in question.

$( "a.tiltAction" ).mousedown(function(event) {
	  	var elem = $(this).closest(".tiltWrapper");
	  	$(elem).addClass("pressed");
  	});
  	
  	$( "a.tiltAction" ).mouseup(function(event) {
	  	var elem = $(this).closest(".tiltWrapper");
	  	$(elem).removeClass("pressed");
  	});
  	
  	$("a.tiltAction").mouseout(function(event) {
	  	var reflectionelem = $(this).next().find(".reflection");
	  	$(reflectionelem).css("opacity","0");	
  	});
  
  	$( "a.tiltAction" ).mousemove(function( event ) {
	  var rotateelem = $(this).next(".tiltPanel");
	  var reflectionelem = $(this).next().find(".reflection");
	  
	  
	  var maxdeg = $(this).closest(".tiltWrapper").attr("data-maxangle");
	  
	  if(!maxdeg) {
		 
		//default to 10deg throw when no max angle is defined in attribute.
	  	var maxdeg = 10;
	  }
	  
	  var elemoffset = $(this).offset();
	  var elemwidth = $(this).width();
	  var elemheight = $(this).height();
	 
	  var cursorX = event.pageX - elemoffset.left;
	  var cursorY = event.pageY - elemoffset.top;
	  
	  
	  if(cursorX < (elemwidth/2)) {
		  var perX = (cursorX / (elemwidth/2))-1;
		  var opacityX = 1-(cursorX / (elemwidth/2));
		  var degX = Math.floor(maxdeg * perX);
		  var refX = elemwidth - cursorX;
		  
	  } else {
		  var perX = 1-(cursorX / (elemwidth/2));
		  var opacityX = (cursorX / (elemwidth/2))-1;
		  var degX = Math.floor(-(maxdeg * perX));
		  var refX = elemwidth - cursorX;
	  }
	  
	  if(cursorY < (elemheight/2)) {
		  var perY = 1-(cursorY / (elemheight/2));
		  var degY = Math.floor(maxdeg * perY);
		  var refY = elemheight - cursorY;
	  } else {
		  var perY = (cursorY / (elemheight/2))-1;
		  var degY = Math.floor(-(maxdeg * perY));
		  var refY = elemheight - cursorY;
	  }
	  
	  
	  var rotation = "transform: rotateX("+degY+"deg) rotateY("+degX+"deg)";
	  $(rotateelem).attr("style",rotation);
	  
	  var reflection = "transform: translate("+refX+"px, "+refY+"px); opacity: "+opacityX+"";
	  $(reflectionelem).attr("style",reflection);
  
	});
	
	$( "a.tiltAction" ).mouseout(function() {
		var rotateelem = $(this).next(".tiltPanel");
		
		var rotation = "transform: rotateX(0deg) rotateY(0deg); transition: 1.0s transform";
		$(rotateelem).attr("style",rotation);
	});

The scripts above cover the math needed to change the 3D rotation of the container in both X and Y directions as well as resetting the position upon the cursor leaving the container area.

The Polish

Last but not least a radial gradient SVG was added to give the reflectivity effect so that the tilting panel appeared to be affected by a light source.

The best part about the scripts above is that since it does its measuring on the fly you can change the container size at any time to anything and it will continue to work.

Conclusion

I quite like how the effect turned out and I may end up using it on an upcoming project. If you have any questions or ideas for improving it drop me a line or leave a comment below.