Home > Software design >  Tilt element pane on mousemove
Tilt element pane on mousemove

Time:06-28

I'm trying to redo the animation that I saw on a site where an image changes it's x and y values with the movement of the mouse. The problem is that the origin of the mouse is in the top left corner and I'd want it to be in the middle. To understand better, here's how the mouse axis values work : mouse axis w/ origin top left

Now here's how I'd want it to be:

mouse axis w/ origin in midle sorry for the bad quality of my drawings, hope you understand my point from those ^^

PS: I'm having a problem while trying to transform the x y values at the same time and I don't know why.

Here's what I wrote in JavaScript :

document.onmousemove = function(e){
  var x = e.clientX; 
  var y = e.clientY; 
  document.getElementById("img").style.transform = "rotateX(" x*0.005 "deg)";
  document.getElementById("img").style.transform = "rotateY(" y*0.005 "deg)";
}

CodePudding user response:

The exact 3D effect you're up to is called "tilting".

Long story short, it uses CSS transform's rotateX() and rotateY() on a child element inside a perspective: 1000px parent. The values passed for the rotation are caltulated from the mouse coordinates inside the parent Element and transformed to a respective degree value.

Here's a quick simplified remake example of the original script:

const el = (sel, par) => (par || document).querySelector(sel);

const elWrap = el("#wrap");
const elTilt = el("#tilt");
const settings = {
  reverse: 0,        // Reverse tilt: 1, 0
  max: 35,           // Max tilt: 35
  perspective: 1000, // Parent perspective px: 1000
  scale: 1,          // Tilt element scale factor: 1.0
  axis: "",          // Limit axis. "y", "x"
};

elWrap.style.perspective = `${settings.perspective}px`;

const tilt = (evt) => {
  const bcr = elWrap.getBoundingClientRect();
  const x = Math.min(1, Math.max(0, (evt.clientX - bcr.left) / bcr.width));
  const y = Math.min(1, Math.max(0, (evt.clientY - bcr.top) / bcr.height));
  const reverse = settings.reverse ? -1 : 1;
  const tiltX = reverse * (settings.max / 2 - x * settings.max);
  const tiltY = reverse * (y * settings.max - settings.max / 2);
  elTilt.style.transform = `
    rotateX(${settings.axis === "x" ? 0 : tiltY}deg)
    rotateY(${settings.axis === "y" ? 0 : tiltX}deg)
    scale(${settings.scale})
  `;
}

elWrap.addEventListener("mousemove", tilt);
/*QuickReset*/ * {margin:0; box-sizing: border-box;}

html, body { min-height: 100vh; }

#wrap {
  height: 100vh;
  display: flex;
  background: no-repeat url("https://i.stack.imgur.com/AuRxH.jpg") 50% 50% / cover;
}

#tilt {
  outline: 1px solid red;
  height: 80vh;
  width: 80vw;
  margin: auto;
  background: no-repeat url("https://i.stack.imgur.com/wda9r.png") 50% 50% / contain;
}
<div id="wrap"><div id="tilt"></div></div>

Regarding your code:

  • Avoid using on* event handlers (like onmousemove). Use EventTarget.addEventListener() instead — unless you're creating brand new Elements from in-memory. Any additionally added on* listener will override the previous one. Bad programming habit and error prone.
  • You cannot use style.transform twice (or more) on an element, since the latter one will override any previous - and the transforms will not interpolate. Instead, use all the desired transforms in one go, using Transform Matrix or by concatenating the desired transform property functions like : .style.transform = "rotateX() rotateY() scale()" etc.

Disclaimer: The images used in the above example are from the original problem's reference website https://cosmicpvp.com might be subject to copyright. Here are used for illustrative and educative purpose only.

CodePudding user response:

You can find out how wide / tall the screen is:

const width = window.innerWidth;
const height = window.innerHeight;

So you can find the centre of the screen:

const windowCenterX = width / 2;
const windowCenterY = height / 2;

And transform your mouse coordinates appropriately:

const transformedX = x - windowCenterX;
const transformedY = y - windowCenterY;

Small demo:

const coords = document.querySelector("#coords");

document.querySelector("#area").addEventListener("mousemove", (event)=>{
  const x = event.clientX;
  const y = event.clientY;
  
  const width = window.innerWidth;
  const height = window.innerHeight;
  
  const windowCenterX = width / 2;
  const windowCenterY = height / 2;
  
  const transformedX = x - windowCenterX;
  const transformedY = y - windowCenterY;
  
  coords.textContent = `x: ${transformedX}, y: ${transformedY}`;
});
body, html, #area {
  margin: 0;
  width: 100%;
  height: 100%;
}

#area {
  background-color: #eee;
}

#coords {
  position: absolute;
  left: 10px;
  top: 10px;
}
<div id="area"></div>
<div id="coords"></div>

CodePudding user response:

I think I would use the bounding rect of the image to determine the center based on the image itself rather than the screen... something like this (using CSSVars to handle the transform)

const img = document.getElementById('fakeimg')
addEventListener('pointermove', handler)


function handler(e) {
  const rect = img.getBoundingClientRect()
  const x1 = (rect.x   rect.width / 2)
  const y1 = (rect.y   rect.height / 2)
  const x2 = e.clientX
  const y2 = e.clientY
  let angle = Math.atan2(y2 - y1, x2 - x1) * (180 / Math.PI)   90
  angle = angle < 0 ?
    360   angle :
    angle

  img.style.setProperty('--rotate', angle);
}
*,
*::before,
*::after {
  box-sizeing: border-box;
}

html,
body {
  height: 100%;
  margin: 0
}

body {
  display: grid;
  place-items: center;
}

[id=fakeimg] {
  width: 80vmin;
  background: red;
  aspect-ratio: 16 / 9;
  --rotation: calc(var(--rotate) * 1deg);
  transform: rotate(var(--rotation));
}
<div id="fakeimg"></div>

  • Related