Usability Hell:
How to make a completely unusable homepage in CSS3 and jQuery

Usability Hell: How to make a completely unusable homepage in CSS3 and jQuery

One of my favorite bits of code I’ve ever done is on the front page of my jiggy, ancient, perpetually-under-construction, virtually-never-viewed old personal website, Life In A Mikeycosm. Eager to make my front page “pop” a little more back in days gone by, I came up with a clever CSS3/javascript hack that caused the page text and logo to slip and slide in three dimensions in response to your mouse movements, rendering the page somewhere between difficult and totally unusable for actually navigating the content of the site. I’ve always been inordinately fond of this.

I’ve embedded it below so you can experience the source of my amusement. Actually, if you click through the above link to the real site, at the bottom left there’s a “Stop this crazy thing” link that stops the animation and moves the links into rows so they’re readable and clickable. I don’t know if the below embed is big enough to show this, though. When animated, the moving links are indeed clickable, if you can catch one with your mouse to click on it. I added a glow-on-hover text effect to indicate when the mouse is over a link and it’s clickable.

Really, I find the whole thing pretty entertaining.

An explanation of how it’s done is below. There’s a “stop this crazy” thing link in the lower left, but depending on your screen dimensions it might be cut off, you might need to stretch the embedded view by dragging the little resize nubbin at the bottom right corner to see the whole kit & kaboodle (Note to web developers: yes, it’s a resizable iframe. Eat that!)

How It’s Done:

The html layout of the page is extremely simple. It’s a couple of section headings, followed by links, laid out in a pretty simply, like this:

<nxbr><b>Section 1:</b>&nbsp; &nbsp;
<a href="innerpage1.html">Page 1 title</a>
</nxbr>
&nbsp; &nbsp;<wbr>
<nxbr><a href="innerpage2.html">Page 2</a></nxbr>
&nbsp; &nbsp;<wbr>
<nxbr><a href="innerpage3.html">Page 3</a></nxbr> 

The only thing that bears some explanation is those custom <nxbr> tags. The reason for those is, this site actually dates back to the very earliest days of my web development, back in the mid ’90s. For a while, before I made the page dynamic and jiggery, I just wanted to control the wrapping so titles didn’t break across lines, and those started life as <nobr> tags. Over time <nobr> tags came to be deprecated, and I’m sure as I was trying to animate things there were some conflicts, or something—this was all decades ago now—and I wound up using CSS to format things how I wanted, but left the tags in, converted to absolutely meaningless <nxbr> tags, because in my neophyte days I guess I wasn’t yet aware you could do <div class="..."> or <span class="..."> or something. Anyway, at this point, all <nxbr>…</nxbr> is doing is marking the individual links, because I want to animate them separately.

Then, the magic starts with this CSS:



nxbr {
	display: inline-block;
};

body {
	position: absolute;
	perspective: 300px;
	width: 100%;
	height: 100%;
	overflow: hidden;
}

nxbr {
	display: inline-block;
	-webkit-transition: all 1000ms;
	-moz-transition: all 1000ms;
	-o-transition: all 1000ms;
	transition: all 1000ms;
	margin-top: .5em;
	margin-bottom: .5em;
}

nxbr:nth-of-type(3n) { //every third nxbr
	transform: rotateX(-5deg) rotatey(-10deg) translatez(45px);
}

nxbr:nth-of-type(3n+1) { //one after every third nxbr
	transform: rotateX(0deg) translatez(50px);
}

nxbr:nth-of-type(3n+2) { //second after every third nxbr
	transform: rotateX(5deg) rotatey(10deg) translatez(55px);
}

#mkTitle {
	// this is the background image display: block;
	position: absolute;
	opacity: .2;
	margin: 100px 15px;
	pointer-events: none;
	width: 90%;
	height: 90%;
	//set the image to move nice & slowly 
	-webkit-transition: all 11500ms;
	-moz-transition: all 11500ms;
	-o-transition: all 11500ms;
	transition: all 11500ms;
	z-index: -10000;
}

Those nxbr:nth-of-type(3n) rules create three different alternating sets of tags, defined individually by those <nxbr> tags.

Then, having already loaded jQuery, this script tells the four CSS rules how to animate the <nxbr> tags to which they apply:



//<![CDATA[
$(document).ready(function() {
    mousePerspective($(window).width(), $(window).height());

    function mousePerspective(w, h) { //begin mouse movement event handler.
        $(document).mousemove(function(e) {
            //this function inherits "e", an object containing pageX and pageY, coordinates of the mouse.

            var Xmouse = e.pageX;
            var Ymouse = e.pageY;
            var Xperc = Xmouse / w;
            var Yperc = Ymouse / h;
            Xpos = Math.floor(Xperc * 100);
            Ypos = Math.floor(Yperc * 100);
            Xrev = 100 - Xpos;
            Yrev = 100 - Ypos;

            $("body,table,nxbr,span,a").css({
                perspectiveOrigin: 12.5 * Xpos + '% ' + 12.5 * Ypos + '%',
                'transform-style': 'preserve-3d',
                '-moz-transform-style': 'preserve-3d',
                '-webkit-transform-style': 'preserve-3d'
            });

// Now we do fancy math to transform SOME (but not all) of the 3D Coordinates of nth-of-type(4n) nxbr tags.  Yes, the CSS has a repeating cycle of three groups of tags, but the jQuery changes parameters in a repeating group of 4. Look at me, I'm the King Crimson of web design!

            $("nxbr:nth-of-type(4n):not('.normal')").css({ 
                'transform': 'perspective(300px) rotateX(' + (-5 * Xpos / 100) + 'deg) rotateZ(' + (-50 * Xpos / Ypos) + 'deg) translatez(' + 345 * Math.sin(Ypos) * Xpos / 70 + 'px)'
            });

            $("nxbr:nth-of-type(4n+1):not('.normal')").css({ //color:'blue',
                'transform': 'perspective(300px) rotateX(' + (25 * (Xpos + 50) / 100) + 'deg) rotatey(' + (250 * (Ypos - 50) / 100) + 'deg) translatez(' + (55 + Math.tan(Xpos)) + 'px)'
            });

            $("nxbr:nth-of-type(4n+2):not('.normal')").css({ //color:'red',
                'transform': 'perspective(300px) rotateX(' + (-15 * (Ypos * Xpos + 50) / 100) + 'deg) rotatey(' + (-10 * (Ypos + 50) / 100) + 'deg) translatez(45px)' //you can turn -10 to -360 or more for drastic rotation effects. Or you can add 360 intead of multiplying it, to make it spin once before settling on less of a drastic rotation
                    //need to include perspective in each declaration because in Firefox it doesn't inherit down past immediate children of element it's applied to
            });

            $("nxbr:nth-of-type(4n+3):not('.normal')").css({ //color:'purple',
                'transform': 'perspective(300px) rotateX(' + (7 * (Xpos / 50) / 100) + 'deg) rotatey(' + (360 + -15 * (Math.tan(Ypos) * 100 - 50) / 100) + 'deg) translatez(' + (25 * (Xpos / (Ypos + 200) + .5)) + 'px)'
            });

        });

    }
});

//]]>

That’s about it. There’s plenty of cruft in there, and if you look at the page code it’s even messier… it includes javascript libraries it doesn’t need, etc. It’s pretty obvious I was still figuring all this out back then. But, hey, it works! Just ask anyone who’s tried to click one of the links to access one of the internal pages, if anyone ever had.

There was just a little bit more CSS and jQuery, to get the “stop” button working, which I’ll add here at a later date.