In the third part, I implement the interaction with JS.
DO IT (5 part series)
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
windowevents - Events: defines callback functions for
windowevents
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,getTransformValueandgetTextShadowValueto generate the new values of the properties of thediv.containeranddiv.textelements - 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.