3D Inset Parallax Effect

Chris Coyier //

The following is a guest post by Joshua Bader. Joshua noticed that certain 3D effects on the web could benefit from adjusting perspective as the web page is scrolled. I'll let him explain.

People love to make flat things appear as if they're three-dimensional. There are two ways to pull off this effect in a 2D environment, shape and movement.

By give a flat shape the appearance of edges or sides, we can make buttons or other objects appear as if they are raised or sunken into the screen. This is seen even here on CSS-Tricks:

The movement effect is a bit trickier. In a three-dimensional world, objects closer to you move fast, while the ones further away move slow. The further away an object is, the slower it moves. This is called the parallax effect. The parallax effect has been used in all sorts of places for the past few years.

The first method, shape, works because it makes things appear to be popping out, or pushed away from you. The second method, movement, works because it makes things seem as if they are positioned in context with each other. But, what if we combined them. An object that looks sunken into the screen, like a shelf, and its edges shrink and grow, using the parallax effect, making the object appear as if it really is in 3D space.

First we need to add some markup on our page and give the items we want to affect the class of inset. Any block level elements will do. Here is a simple form with inputs that will have the effect applied.

<form>

  <label>
    First Name:
    <input class="inset" type="text" placeholder="Enter First Name" />
  </label>

  <label>
    Last Name:
    <input class="inset" type="text" placeholder="Enter Last Name" />
  </label>

  <label>
    Email Address:
    <input class="inset" type="text" placeholder="Enter Email Address" />
  </label>

  <label>
    Address:
    <input class="inset" type="text" placeholder="Enter Address" />
  </label>

  <label>
    Phone Number:
    <input class="inset" type="text" placeholder="Enter Phone Number" />
  </label>

</form>

Next we will need to style our inset elements. By giving the item slightly different color borders we can give the element the illusion of depth. This is because the joint between borders is an angle and with the different colors, it appears as if light is hitting different "sides" of a 3D area. This is the 'shape' method we mentioned earlier.

.inset {
  width: 100%;
  box-sizing: border-box;
  background: #f9f9f9;
  border-style: solid;
  border-width: 30px;
  border-top-color: #e5e5e5;
  border-right-color: #eee;
  border-bottom-color: #e5e5e5;
  border-left-color: #eee;
}

Now that the shape we want to manipulate is styled, we can use some simple jQuery to get the element's original border-width, window height, scroll position, target element's position on screen and convert that position to a percentage for the top edge and bottom edge.

var origBorderWidth = parseInt($('.inset').css('border-top-width')),
win = $(window),
windowHeight = win.height();

$('.inset').each(function() {
  var self = $(this),
    scrollHeight = win.scrollTop(),
    elementPosition = self.position(),
    positionPercentTop = Math.round((elementPosition.top - scrollHeight) / windowHeight * 100);
    positionPercentBottom = Math.round((elementPosition.top + self.outerHeight() - scrollHeight) / windowHeight * 100);
});

Once we have all of these numbers as variables, we can resize the top and bottom border-widths of each element based on its position on the screen.

var origBorderWidth = parseInt($('.inset').css('border-top-width')),
win = $(window),
windowHeight = win.height();

$('.inset').each(function() {
  var self = $(this),
    scrollHeight = win.scrollTop(),
    elementPosition = self.position(),
    positionPercentTop = Math.round((elementPosition.top - scrollHeight) / windowHeight * 100);
    positionPercentBottom = Math.round((elementPosition.top + self.outerHeight() - scrollHeight) / windowHeight * 100);

  self.css({
    'border-top-width' : origBorderWidth - (origBorderWidth * (positionPercentTop / 100)) + 'px',
    'border-bottom-width' : origBorderWidth * (positionPercentBottom / 100) + 'px'
  });
});

Now comes the fun part – recalculating each element's top and bottom border-widths while the page is scrolled. This will give us the desired parallax, 'movement' effect.

var origBorderWidth = parseInt($('.inset').css('border-top-width')),
  win = $(window);

function set3D() {
  var windowHeight = win.height();

  $('.inset').each(function() {
    var self = $(this),
      scrollHeight = win.scrollTop(),
      elementPosition = self.position(),
      positionPercentTop = Math.round((elementPosition.top - scrollHeight) / windowHeight * 100);
      positionPercentBottom = Math.round((elementPosition.top + self.outerHeight() - scrollHeight) / windowHeight * 100);

    self.css({
      'border-top-width' : origBorderWidth - (origBorderWidth * (positionPercentTop / 100)) + 'px',
      'border-bottom-width' : origBorderWidth * (positionPercentBottom / 100) + 'px'
    });
  });
};

win.on('load scroll resize', function() {
  set3D();
});

There you have it. To see the effect, keep you eye on a single element as it moves up and down the page when scrolled. You will see its top and bottom border-width change depending on its position on the page. Its subtle, but effective.

Check out this Pen!
Editors note: pretty neat eh? Perhaps with even more tech we could create a demo that did eye-tracking and adjusted the perspectives depending on where your eyes are in relation to the screen. Here's another example that adjusts perspective based on the mouse.