Skip to main content

DO IT part 3: JS

· 5 min read

In the third part, I implement the interaction with JS.


JS sections

I separated the JS file into six sections:

  1. State: object that stores the cursor positions
  2. Elements: HTML elements that will be manipulated
  3. Functions: functions used to manipulate the HTML elements and handle the cursor positions
  4. Rendering: function that renders the three-dimensional text
  5. Handlers: functions that handle window events
  6. Events: defines callback functions for window events

State

Object used to store the current and target position of the cursor. The target position is the actual cursor position, while the current position is the position with inertia. This creates a smooth motion when moving the cursor. Inertia is calculated by the getPositionWithInertia function.

index.js
/* ========================================   State======================================== */const state = {  current: { x: 0, y: 0 },  target: { x: 0, y: 0 },};

Elements

The container element has the transform property manipulated, while the text element has the text-shadow property manipulated.

index.js
/* ========================================   Elements======================================== */const container = document.querySelector(".container");const text = document.querySelector(".text");

Functions

getTextShadowValue

The getTextShadowValue function generates the value for the text-shadow property of the div.text element, the value is based on the cursor position.

The function receives the parameters xPercentage and yPercentage, where xPercentage ranges from -100% (left) to 100% (right), and yPercentage ranges from -100% (bottom) to 100% (top).

The percentage refers to the cursor position. For example, if the cursor is in the center of the viewport, both percentages are 0%. If the cursor is in the top left of the viewport, xPercentage is -100% and yPercentage is 100%.

The function creates 50 shadows, where the last shadow is positioned at most 2vw (2 viewport width units).

getTransformValue

The getTransformValue function generates the value for the transform property of the div.container element, the value is based on the cursor position.

Like the getTextShadowValue function, the getTransformValue function receives the xPercentage and yPercentage parameters.

The function creates a rotation of up to 20 degrees.

getPositionWithInertia

The getPositionWithInertia function generates the value for state.current.x and state.current.y, receiving the parameters current and target.

The function calculates the distance between the two points. If the distance is too small, the target position is returned. Otherwise, 10% of the distance is added to the current position, creating a smooth movement.

updateTarget

The updateTarget function maps the cursor position to state.target.x and state.target.y. The function is used in the mousemove event of window.

index.js
/* ========================================   Functions======================================== */function getTextShadowValue(xPercentage, yPercentage) {  const shadowAmount = 50;  const maxVw = 2;  let result = "";  for (let index = 0; index < shadowAmount; index++) {    const isLast = index === shadowAmount - 1;    const percentage = (index + 1) / shadowAmount;    const xValue = maxVw * percentage * (xPercentage * -1);    const yValue = maxVw * percentage * yPercentage;    result +=      `${xValue}vw ` +      `${yValue}vw ` +      `var(--foreground-color)${isLast ? "" : ","}`;  }  return result;}function getTransformValue(xPercentage, yPercentage) {  const maxDegrees = 20;  const rotateXValue = yPercentage * maxDegrees;  const rotateYValue = xPercentage * maxDegrees;  return `rotateX(${rotateXValue}deg) rotateY(${rotateYValue}deg)`;}function getPositionWithInertia(current, target) {  const distance = target - current;  if (Math.abs(distance) < 0.01) {    return target;  }  return current + distance * 0.1;}function updateTarget(event) {  state.target.x = event.x;  state.target.y = event.y;}

Rendering

The render function is executed every animation frame using requestAnimationFrame, which synchronizes the rendering with the screen refresh rate. The highlights are:

  • Line 6: the function only re-renders when the current cursor position is different from the target position
  • Lines 10, 11, 22 and 23: uses the functions getPositionWithInertia, getTransformValue and getTextShadowValue to generate the new values ​​of the properties of the div.container and div.text elements
  • Lines 26 and 27: updates the current cursor position
  • Line 31: requests an animation frame by calling itself, creating an infinite rendering loop
index.js
/* ========================================   Rendering======================================== */function render() {  if (    state.current.x !== state.target.x ||    state.current.y !== state.target.y  ) {    const x = getPositionWithInertia(state.current.x, state.target.x);    const y = getPositionWithInertia(state.current.y, state.target.y);    const halfWidth = window.innerWidth * 0.5;    const halfHeight = window.innerHeight * 0.5;    // left is negative, right is posivite    const xPercentage = (x - halfWidth) / halfWidth;    // bottom is negative, top is posivite    const yPercentage = ((y - halfHeight) / halfHeight) * -1;    // update dom    container.style.transform = getTransformValue(xPercentage, yPercentage);    text.style.textShadow = getTextShadowValue(xPercentage, yPercentage);    // update state    state.current.x = x;    state.current.y = y;  }  // render loop  requestAnimationFrame(render);}

Updating only the transform property



Updating only the text-shadow property



Updating both transform and text-shadow

Handlers and Events

In this section the callbacks for the mousemove and load events of window are defined. In the mousemove event, the target cursor position is updated, and in the load event, rendering is started.

index.js
/* ========================================   Handlers======================================== */function handleMouseMove(event) {  updateTarget(event);}function handleLoad(event) {  render();}/* ========================================   Events======================================== */window.addEventListener("mousemove", handleMouseMove);window.addEventListener("load", handleLoad);

Inertia: comparison

Inertia brings smoothness to movement.

Without inertia



With inertia

Conclusion

With the JS defined, I considered the project complete.

In the conclusion, I write my final thoughts: DO IT conclusion: 3D text with CSS and JS.

Recommended reading