Share onTwitterFacebookGoogle+

Recently one of my students asked if it was possible to free-rotate an element on screen using mouse or touch movement. As my first semester classes concentrate on just HTML5 and CSS, the answer had to be “No, not without JavaScript”… but this solution needs only a few lines of JS, and this variation has the interesting addition of CSS variables.

Objects in Space

The demo features just a single element on the page:

<img src="dasha.jpg" srcset="dasha-2x.jpg 2x" alt>

(Of course, the page could have as many elements you wish; I’ve eliminated everything else for the sake of simplicity and clarity).

The body is set to 0 margin and a min-height of 100vh , and uses flexbox to center the element on the page:

body { min-height: 100vh; display: flex; justify-content: center; align-items: center; perspective: 60vw; margin: 0; --mouseX: 0deg; --mouseY: 0deg; }

In addition, a CSS 3D perspective is set, together with two CSS variables. These variables are used to style the image:

img { width: 80vmin; height: 80vmin; transform: rotateX(calc(var(--mouseY))) rotateY(calc(var(--mouseX))); }

Note that because we’re using more than one variable in the transform property, we must surround each with calc() to avoid a syntax error. In the near future, when rotateX and Y become their own CSS properties, this won’t be necessary. The image needs both a width and height, as otherwise flexbox will stretch the image.

Position Tracking

The JavaScript starts by identifying the image:

const img = document.getElementsByTagName("img")[0];

While it’s common for CSS variables can be defined and used solely inside of a stylesheet, it’s also possible to set them from JavaScript, using setProperty . I’ve done so inside a function that takes the current position of the pointer, determines the height and width of browser window (and divides it in half), then uses this information to determine the correct amount of rotation for the image:

function sway(xPos, yPos) { let wh = window.innerHeight / 2, ww = window.innerWidth / 2; document.body.style.setProperty("--mouseX", (xPos - ww) / 25+"deg"); document.body.style.setProperty("--mouseY", (yPos - wh) / 25+"deg"); }

This function is initiated by both mouse and touch movement:

document.addEventListener("mousemove", function(e) { sway(e.clientX,e.clientY); }) document.addEventListener("touchmove", function(e) { e.preventDefault(); var touch = e.targetTouches[0]; if (touch) { sway(touch.pageX, touch.pageY); } });

Note that the values used in the variables are swapped: movement in X creates rotation of the image in Y, and movement in Y creates rotation in X.

The Power of Variables

As Lea Verou pointed out in her recent CSS Conf presentation, browser’s near-universal support for CSS variables today allows styles to move back into the position they are meant to hold - that of presentation - while JavaScript can take its traditional role of behaviour. CSS variables allow a seamless communication between the two.

If you did need support in MS Edge and IE, (and get around the current limitations of Firefox) you could write the function solely in JavaScript:

function sway(xPos, yPos) { img.style.transform = "rotateX("+(yPos - wh) / 25+"deg) rotateY("+(xPos - ww) / 25 +"deg)"; }

I’ve also created a CodePen of this version, in addition to the original code.

Photograph by Sean Archer, used under a Creative Commons license