3D Button Parallax

Guest Author //

The following is a guest post by Alexander Futekov. We recently published an article by Joshua Bader in which a 3D inset look was adjusted as the page scrolled to give it a more realistic interaction. This is similar only Alexander is using an extruded look on buttons and employing a totally different technique.

The introduction of CSS3 resulted in the explosion of beautiful and interesting buttons, styled with gradients, shadows, and borders - often to achieve a 3D effect. The problem with such 3D buttons is a static perspective. The 3D is not always very convincing when you can only see its front and perhaps a bit from an edge or two - even when the page moves and the position of the button on the page moves.

Fortunately, we can achieve a realistic perspective effect with the help of 3D transforms to adjust the sides of the buttons. We can do this without modifying existing HTML, by using pseudo elements for the sides. Then bring forward the button itself with translateZ.

Before we start, we must first solve a problem. The perspective origin point is usually set at the vertical and horizontal center of the element. This means that when we set it based on the body we will get something like this:

By setting the height of the body to be 100%, we can fix the perspective origin point to the center of the viewport like this:

html {
  height: 100%;
  overflow: hidden;
}

body {
  height: 100%;
  overflow-y: scroll;
}

For a simple <button>, here's the CSS:

button {
  position: relative;
  display: inline-block;
  padding: 4px 16px;
  border: 0;
  background-color: blue;
  background-image: radial-gradient(ellipse at top, rgba(255, 255, 255, 0.15) 50%, transparent);
  color: #222;
  transform-style: preserve-3d;
  transform: translateZ(20px);
}

button::before {
  content: "";
  position: absolute;
  width: 100%;
  height: 100%;
  left: 0;
  top: 0;
  transform-origin: 0% 0%;
  transform: rotateX(-90deg);
}

button::after {
  content: "";
  position: absolute;
  width: 100%;
  height: 100%;
  left: 0;
  top: 0;
  transform-origin: 0% 0%;
  transform: rotateY(90deg);
}

But how do we get 4 sides for each button with just 2 pseudo elements? JavaScript! if we want to avoid adding extra wrappers to each button (we do) we need scripting to move the top side down and the left side to the right depending on where the button is relative to the user's viewport. The script for switching the top with the bottom side of the button is pretty straightforward - changing top/bottom or right/lest sides occurs only on load and on resize, and top/bottom only on scroll:

$(window).on("load resize", function() {
  topHalf();
  leftHalf();
});

$("body").scroll(function() {
  topHalf();
});

function topHalf() {
  $("button").each(function() {
    var self = $(this),
        offTop = self.offset().top,
        scrTop = $(window).scrollTop(),
        halfWindowHeight = ($(window).height())/2;

    self.toggleClass("top-half", (offTop - scrTop) < halfWindowHeight);
  });
}

function leftHalf() {
  $("button").each(function() {
    var self = $(this),
        offLeft = self.offset().left,
        halfWindowWidth = ($(window).width())/2;

    self.toggleClass("left-half", offLeft < halfWindowWidth);
  });
}

Editor's note: This could certainly be refactored a bit to lend structure and gain some efficiency. Feel free to fork the Pen below and have a go!

Some new classes will help position the sides:

button.top-half::before {
  top: auto;
  bottom: 0;
  transform-origin: 100% 100%;
  transform: rotateX(90deg);
}

button.left-half::after {
  left: auto;
  right: 0;
  transform-origin: 100% 100%;
  transform: rotateY(-90deg);
}

Keep in mind that if you use classes to float the buttons (for example .pull-left and .pull-right like in Twitter Bootstrap) you will know where each button is positioned horizontally and thus be able to skip half of the JavaScript code.

Here's a styled demo to play around with. It has an animated push-down effect for the buttons and includes some other 3D elements that don't requre any JavaScript:

Check out this Pen!

A similar technique can also be used to replicate the 3D Inset Parallax Effect in a very rough way. It's a bit more complex, but doesn't require any JavaScript:

Check out this Pen!