Skip to content

Instantly share code, notes, and snippets.

@mikeybob
Created April 21, 2024 01:49
Show Gist options
  • Save mikeybob/ba3509fe3ebe340d9ca66b82321a260d to your computer and use it in GitHub Desktop.
Save mikeybob/ba3509fe3ebe340d9ca66b82321a260d to your computer and use it in GitHub Desktop.
Revolving Text
<div class="bg-lighting"></div>
const animationDuration = 24000;
const updateInterval = 400;
let startTimeMap = new Map();
let intervalMap = new Map();
function createLetters(text, rad, offset, frontColor, direction, id) {
const radius = rad;
let letters = text.split('');
if(direction != 'backwards') { letters = letters.reverse(); }
let dir = ( direction == 'backwards') ? -1 : 1;
const container = document.createElement('div');
container.classList.add('text-container');
container.classList.add(id);
document.body.appendChild(container);
startTimeMap.set(id, Date.now());
letters.forEach((letter, index) => {
const angle = (360 / letters.length) * (dir) * index - 8;
const radian = angle * (Math.PI / 180);
const x = radius * Math.cos(radian);
const y = radius * Math.sin(radian);
const letterElement = document.createElement('div');
letterElement.classList.add('letter');
letterElement.classList.add(id);
letterElement.textContent = letter;
letterElement.setAttribute("data-text", letter);
letterElement.style.transform = `translate(-50%, -50%) translate(${x}px, ${y}px) rotateX(90deg) rotateY(${angle + 90}deg) rotateZ(180deg) translateY(${offset}px)`;
container.appendChild(letterElement);
});
if (!intervalMap.has(id)) {
const intervalId = setInterval(() => {
updateLetters(container, letters.length, 0.54, 1, frontColor, dir, id);
}, updateInterval);
intervalMap.set(id, intervalId);
}
}
function updateLetters(container, totalLetters, op1, op2, colorFront, dir, id) {
const startTime = startTimeMap.get(id);
const elapsedTime = Date.now() - startTime;
const rotation = (elapsedTime % animationDuration) / animationDuration * 360;
const activeAngleRange = 180;
const letters = container.querySelectorAll(`.${id}`);
letters.forEach((letter, index) => {
const letterAngle = 270 + (360 / totalLetters * index);
const angleDifference = Math.abs(rotation - letterAngle) % 360;
let isActive = angleDifference < activeAngleRange / 2 || angleDifference > 360 - activeAngleRange / 2;
let activeDirection = dir === 1 ? !isActive : isActive;
letter.style.opacity = activeDirection ? op1 : op2;
letter.style.color = activeDirection ? '' : colorFront;
});
}
function isMobileDevice() {
return (typeof window.orientation !== "undefined") || (navigator.userAgent.indexOf('IEMobile') !== -1);
}
if (isMobileDevice()) {
createLetters(" ⟩ Revolving Text ⟩ Revolving Text", 124, 0, 'white' , 'forwards', 'set1');
} else {
createLetters(" ⟩ Revolving Text ⟩ Revolving Text", 124, 0, 'white' , 'forwards','set1');
createLetters(" ⟨ Revolving Text ⟨ Revolving Text ⟨ Revolving Text", 200, 0, 'white', 'backwards','set2');
}
function updateText(newText) {
document.querySelectorAll('.text-container').forEach(container => {
container.remove();
});
startTimeMap.clear();
intervalMap.forEach(intervalId => clearInterval(intervalId));
intervalMap.clear();
if (isMobileDevice()) {
createLetters(` ⟩ ${newText}${newText}`, 124, 0, 'white' , 'forwards', 'set1');
} else {
createLetters(` ⟩ ${newText}${newText}`, 124, 0, 'white' , 'forwards','set1');
createLetters(` ⟨ ${newText}${newText}${newText}`, 200, 0, 'white', 'backwards','set2');
}
}
/* GUI */
import { GUI } from 'https://cdn.skypack.dev/dat.gui'
const CONFIG = { shadowHeight: 24, fontSize: 26, refOpacity: 0.7, refBlur: 2, newText: 'Revolving Text'}
const CTRL = new GUI()
const UPDATE = () => {
for (const key of Object.keys(CONFIG)) {
document.documentElement.style.setProperty(`--${key}`, CONFIG[key])
}
}
CTRL.add(CONFIG, 'fontSize', 14, 42, 1).name("Font Size").onChange(UPDATE)
CTRL.add(CONFIG, 'shadowHeight', 0, 264, 1).name("Height").onChange(UPDATE)
CTRL.add(CONFIG, 'refOpacity', 0, 1, 0.01).name("Reflection Opacity").onChange(UPDATE)
CTRL.add(CONFIG, 'refBlur', 0, 12, 1).name("Reflection Blur").onChange(UPDATE)
CTRL.add(CONFIG, 'newText').name("Text").onChange(updateText);
UPDATE()
@import url('https://fonts.googleapis.com/css2?family=Poppins&display=swap');
*{
box-sizing: border-box;
}
html, body {
height: 100%;
overflow: hidden;
}
body {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
align-items: center;
margin: 0;
font-family: Poppins;
background: #161616;
--speed: 24s;
}
.bg-lighting {
position: absolute;
pointer-events: none;
width: 100%;
height: 100%;
top: 0;bottom: 0; margin: auto;
background: radial-gradient(
circle at 50% -100%,
rgba(0, 0, 0, 1),
rgba(0, 0, 0, 0)
),
url("data:image/svg+xml,%3Csvg viewBox='0 0 600 600' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E"
);
filter: contrast(100%) brightness(200%) invert(100%) grayscale(1) opacity(0.1);
mix-blend-mode: screen;
z-index: -1;
}
.bg-lighting::after {
display: block; content: '';
width: 100%; height: 100%;
position: absolute;
background: linear-gradient( #000 20%, #0003 80%, transparent 100%);
animation: var(--speed) f-grad linear infinite;
}
@keyframes f-grad {
0% { height: 100%; }
50% { height: 50%; }
100% { height: 100%; }
}
.text-container {
position: absolute;
width: 300px;
height: 300px;
transform-style: preserve-3d;
animation: var(--speed) f-rotate linear infinite;
}
.text-container.set1 {
animation: var(--speed) f-rotate linear infinite reverse;
}
@keyframes f-rotate {
0% { transform: rotateX(80deg) rotateY(0deg) rotateZ(0deg); }
50% { transform: rotateX(42deg) rotateY(0deg) rotateZ(180deg); }
100% { transform: rotateX(80deg) rotateY(0deg) rotateZ(360deg); }
}
.letter {
position: absolute;
top: 50%;
left: 50%;
transform-origin: center;
font-size: calc(var(--fontSize)*1px);
color: #fff;
filter: drop-shadow(0 3px 3px #ffffff9a);
}
.letter::after {
content: attr(data-text);
position: absolute;
transform-origin: top;
top: 100%;
left: 0;
transform: scaleY(-1) translateY(calc((var(--shadowHeight)*-1px) + -82%));
z-index: -1;
color: #fff;
filter: blur(calc(var(--refBlur) * 1px));
opacity: var(--refOpacity);
}
.text-container.set2 .letter {
transform: rotateX(180deg);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment