In the third part, I implement the interaction with JS.
JS sections
I separated the JS file into six sections:
- State: object that stores the cursor positions
- Elements: HTML elements that will be manipulated
- Functions: functions used to manipulate the HTML elements and handle the cursor positions
- Rendering: function that renders the three-dimensional text
- Handlers: functions that handle
window
events - 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.
/* ======================================== 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.
/* ======================================== 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
.
/* ======================================== 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
andgetTextShadowValue
to generate the new values of the properties of thediv.container
anddiv.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
/* ======================================== 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.
/* ======================================== 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.