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!
Really awesome technique there. I never really thought about using a parallax effect like that before. Maybe as a more “fun” use of it would be a pair of eyes on a painting following the user :P Creeepy!
Really cool. And it looks OK in IE! :-) Thanks
Pretty darn cool.
Tacky for my taste.
Yeah looks great!! Tested in IE, looks great also! Cheers for the post
Looks great!
But I don’t see where it could be used in a real life client site.
Cool! Though I think, for people who don’t get the effect, they’ll be like “Whoa, the textboxes are screwed up everytime I scroll!”
Wonderful effect! I’m not a big fan of those 3D elements around this site and I prefer flat elements in my design, but this code would look awesome in some kind of 3D bookshelf.
I just have one question – in your code you “grab” the window element twice without caching it – wouldn’t it be better to cache it outside the function block? Or isn’t this a big performance issue, grabbing it every time (and twice)?
You mean like this…
http://cdpn.io/xhzFC
And yes the window height can be set outside of the each function. I have updated the Pen.
I have also made the window element into a variable at the top.
Guess you should also cache the insets instead of re-selecting them all on each scroll event:
Good call.
Oh, and squeezing out a little bit more – no real need to use Math.round(), eh?
I really don’t like it, usually I always let my eyes looking at the same place on the webpage while I scroll, and I am fairly sure most of people do that. So the effect is not noticeable, it even feel weird and buggy :/
Btw, eye tracking would be useless, even if you move your eyes the parralax doesn’t change. You’d have to do head tracking instead. Like that 3rd video: http://johnnylee.net/projects/wii/
@scarfacedeb
Hey, its common sense, you can use it on your clients site to make it more valuable, elegant design and more stylish.
Hey Chris the new outlook email think doesn’t wrap text and makes it really hard to read your feed. Could you include a css line for that? Cus leave it to Microsoft and it’ll never happen.
In the “Editors Note”, the example mouse perspective works on codepin but not when I copy paste to separate html, css, and script.js files. Using the same browser (chrome). Also tried using the prefixed -webkit-perspective and -webkit-transform:rotate but the page just loads as a bunch of white squares so it’s not even showing the initial perspective:80px though it is loading in styles under dev tools. Looking in dev tools I see -webkit-perspective-origin changing as I move the mouse but the page it’s self is not changing.
Anyone know why it would work in codepin but not in a actual html page?
Thanks
oops, misspelled codepen twice :-)
Lovely idea even if I think the indepth effect is a bit too strong to be visually appealing.
Very lightweight code anyway, this is nice. :)
For a less drastic effect, try using a smaller border-width.
This is pretty awesome. Thank you! But, I will have to say, you are relying on JavaScript to style elements on a page.
As someone who tried to accomplish this effect in CSS alone, I can appreicate the need for JavaScript here. Kudos!
Cute gimmick.
Hehe…this is awesome…thanks alot
This is really great and amazing and especially for those people who are not wizards at creating these kinds of effects.
I took me 10 minutes of intense concentration to figure out what the big deal is here – but now I get it. I can be quite obtuse but make up for it in sarcasm. The effect is almost too subtle here but I see where this could be used to do some cool webnastics. Off to CodePen. Thank you, Chris.
+brian
It was pretty subtle, had to scroll couple of times to notice it (or better, to really LOOK at it ;) ) and to me, anything subtle is a nice add-on to the skillset, might come in handy one day, might not. but the more examples of parallax effects, the better we’ll understand the dynamics.
There’s a small bug – the page height changes depending on your position in it.
To see it scroll to the very bottom and then drag the scrollbar handle up – you’ll notice that the handle decreases in size and gets out of sync with your mouse.
Turns out that when an inset element is off the screen and one of the borders is 0px, the other (bottom or top) continues to grow without limit. On larger pages this will be a serious issue.
Pretty awesome, but using JavaScript making it bit tedious.
hi, do i need to put all of the code in my web page? html, js and css? thanks
Super cool! I’m not sure if I’m a fan of the inset content with the headers “outside” of the inset. Looks a little weird. I could definitely see some design concepts implementing this in cool ways. I’m thinking smooth auto-scrolling from Point A to Point B of the page. The effect looks way awesome if you scrub with the scroll bar handle instead of the mouse wheel.
Thanks for sharing!! The good thing is it will also work in IE8
Tried and tested on IE and FF. Works great. This is really awesome. Thanks