Interactive Ferrofluid separator
As the theme of the website is related to ink, I wanted to have animated waves of ink at the bottom of the page. I found no code which would allow me to do so with randomized wave crests, and then I stumbled upon SVG animations. The following code finds all separator elements on the page and embeds a path element which interacts with the cursor upon movement. The result is a 2D, ferrofluidic ink pool which rises as the mouse moves close to it. Currently the only separator I am using is in my footer, but I thought the idea of having it ready anywhere I want to use a separator is nice. Also when playing around with the element, it seems as if it lifts up the page, as if it is a curtain, or an incision, which I find beautiful. The code went into the footer.
HTML: <div id="wave-container"> <svg id="wave"></svg> </div>
JAVASCRIPT: document.addEventListener("DOMContentLoaded", function() { const container = document.getElementById('wave-container'); container.style.display = 'none'; //check if we are in mobile view const headerActions = document.querySelector('.header-actions'); let isTouch = false; function checkHeader() { const styles = window.getComputedStyle(headerActions); isTouch = styles.getPropertyValue('display') !== 'flex'; } checkHeader(); if (isTouch !== true) { const lines = document.querySelectorAll('.horizontalrule-block'); lines.forEach(line => { const clonedContainer = container.cloneNode(true); line.appendChild(clonedContainer); clonedContainer.style.display = 'block'; const svg = clonedContainer.querySelector('#wave'); const width = clonedContainer.clientWidth; const height = clonedContainer.clientHeight / 2; const waveHeight = 0; const numPoints = 300; const points = []; for (let i = 0; i <= numPoints; i++) { points.push({ x: (i / numPoints) * width, y: height, originalY: height + Math.random() * waveHeight - waveHeight / 2 }); } const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); svg.appendChild(path); function createPath(points) { let d = `M ${points[0].x} ${points[0].y}`; for (let i = 1; i < points.length; i++) { d += ` L ${points[i].x} ${points[i].y}`; } return d; } function animate() { for (let i = 0; i < points.length; i++) { points[i].y += (points[i].originalY - points[i].y) * 0.05; } path.setAttribute('d', createPath(points)); requestAnimationFrame(animate); } clonedContainer.addEventListener('mousemove', function(event) { const rect = clonedContainer.getBoundingClientRect(); const mouseX = event.clientX - rect.left; const mouseY = event.clientY - rect.top; for (let i = 0; i < points.length; i++) { const dx = mouseX - points[i].x; const dy = mouseY - points[i].y; const distance = Math.sqrt(dx * dx + dy * dy); const force = Math.exp(-distance * 0.045); const scale = Math.min(i / numPoints, 1 - (i / numPoints)); points[i].y -= force * 20 * scale; // Apply scaled force } }); animate(); }); } });
CSS: .horizontalrule-block { justify-content: center; position: relative; z-index: 9999999; } #wave-container { width: 75%; height: 200px; display: flex; justify-content: center; align-items: center; position: absolute; left: 50%; transform: translateX(-50%); padding: 0; } #wave-container svg { width: 100%; height: 100%; top: 0; position: absolute; } #wave-container path { fill: #000000; stroke: transparent; stroke-width: 2; }
Notice how there is a variable for the number of points. This allows potential scaling for devices which are less powerful. However, I currently disable this code on mobile as it hits differently on touch screens.
COMMENTS:
Leave a comment: