Created
January 24, 2024 05:54
-
-
Save pbnkp/5d42a827e49be3cc06d21ebe86a20c8d to your computer and use it in GitHub Desktop.
Infinite Marquee Effect Broken Down
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div id="app"></div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'https://cdn.skypack.dev/react' | |
import { render } from 'https://cdn.skypack.dev/react-dom' | |
import tweakpane from 'https://cdn.skypack.dev/tweakpane' | |
import { useTweaks } from 'https://cdn.skypack.dev/use-tweaks' | |
const ROOT_NODE = document.querySelector('#app') | |
const App = () => { | |
const { speed, diff, items, pad, direction, translate, spill, state, reverse, scale, inset, outset } = | |
useTweaks({ | |
speed: 10, | |
spill: false, | |
scale: { value: 1, min: 0.1, max: 2, step: 0.1 }, | |
items: { value: 10, min: 1, max: 20, step: 1 }, | |
state: { | |
value: 'running', | |
options: ['running', 'paused'], | |
}, | |
translate: { | |
value: 'items', | |
options: ['track', 'items'], | |
}, | |
direction: { | |
value: 'horizontal', | |
options: ['horizontal', 'vertical'], | |
}, | |
pad: { value: false, name: 'Pad out' }, | |
diff: false, | |
reverse: false, | |
inset: { value: 0, min: -10, max: 10, step: 0.1 }, | |
outset: { value: 0, min: -10, max: 10, step: 0.1 }, | |
}) | |
const renderStamp = Date.now() | |
return ( | |
<div | |
className="container" | |
data-direction={direction} | |
data-pad={pad} | |
data-pad-diff={diff} | |
data-translate={translate} | |
data-play-state={state} | |
data-spill={spill} | |
data-reverse={reverse} | |
style={{ '--speed': speed, '--count': items, '--scale': scale, '--inset': inset, '--outset': outset }} | |
> | |
<ul> | |
{pad && translate === 'track' | |
? new Array(items).fill(0).map((item, index) => { | |
return ( | |
<li | |
aria-hidden="true" | |
className="pad pad--negative" | |
key={`pad-negative-${renderStamp}--${index}`} | |
> | |
{index} | |
</li> | |
) | |
}) | |
: null} | |
{new Array(items).fill(0).map((item, index) => { | |
return ( | |
<li key={`index-${renderStamp}--${index}`} style={{ '--index': index }}> | |
{index} | |
</li> | |
) | |
})} | |
{pad && translate === 'track' | |
? new Array(items).fill(0).map((item, index) => { | |
return ( | |
<li | |
aria-hidden="true" | |
className="pad pad--positive" | |
key={`pad-positive-${renderStamp}--${index}`} | |
> | |
{index} | |
</li> | |
) | |
}) | |
: null} | |
</ul> | |
</div> | |
) | |
} | |
render(<App />, ROOT_NODE) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@import "normalize.css"; | |
*, | |
*:after, | |
*:before { | |
box-sizing: border-box; | |
} | |
body { | |
display: grid; | |
place-items: center; | |
min-height: 100vh; | |
font-family: monospace , sans-serif, system-ui; | |
overflow: hidden; | |
} | |
body::before { | |
--line: hsl(0 0% 5% / 0.25); | |
--size: 60px; | |
content: ""; | |
height: 100vh; | |
width: 100vw; | |
position: fixed; | |
background: | |
linear-gradient(90deg, var(--line) 1px, transparent 1px var(--size)) 0 -5vmin / var(--size) var(--size), | |
linear-gradient(var(--line) 1px, transparent 1px var(--size)) 0 -5vmin / var(--size) var(--size); | |
mask: linear-gradient(-15deg, transparent 60%, white); | |
top: 0; | |
z-index: -1; | |
} | |
.container { | |
width: 50vmin; | |
outline: 1px solid black; | |
padding: 1rem; | |
border-radius: 6px; | |
container-type: size; | |
scale: var(--scale); | |
} | |
.container[data-spill=true]::after { | |
--padding-x: 1rem; | |
--padding-y: 1rem; | |
content: ""; | |
position: fixed; | |
top: 50%; | |
left: 50%; | |
background: hsl(10 80% 0% / 0.25); | |
width: calc(var(--scale) * 10000vw); | |
height: calc(var(--scale) * 10000vh); | |
pointer-events: none; | |
translate: -50% -50%; | |
mask: | |
linear-gradient(white, white) 50% 50% / 100% 100% no-repeat, | |
linear-gradient(white, white) 50% 50% / calc(100cqi + (var(--padding-x) * 2)) calc(100cqh + (var(--padding-y) * 2)) no-repeat; | |
mask-composite: exclude; | |
} | |
.container:not([data-spill=true]) { | |
resize: both; | |
overflow: hidden; | |
} | |
[data-direction=horizontal] { | |
aspect-ratio: 16 / 9; | |
min-height: 180px; | |
min-width: 300px; | |
} | |
[data-direction=vertical] { | |
aspect-ratio: 9 / 16; | |
min-width: 180px; | |
min-height: 300px; | |
} | |
ul { | |
display: flex; | |
gap: 1rem; | |
padding: 0; | |
margin: 0; | |
list-style-type: none; | |
} | |
[data-reverse=true] * { | |
animation-direction: reverse !important; | |
} | |
[data-translate=track][data-direction=horizontal] ul { | |
--destination-x: -100%; | |
animation: track-translate calc(var(--speed) * 1s) infinite linear; | |
} | |
[data-translate=track][data-direction=vertical] ul { | |
--destination-y: -100%; | |
animation: track-translate calc(var(--speed) * 1s) infinite linear; | |
} | |
[data-translate=track][data-direction=horizontal][data-pad=true] ul { | |
--destination-x: calc((100% / -3) * 2); | |
translate: calc(100% / -3) 0; | |
} | |
[data-translate=track][data-direction=vertical][data-pad=true] ul { | |
--destination-y: calc((100% / -3) * 2); | |
translate: 0 calc(100% / -3); | |
} | |
[data-pad-diff=true] .pad { | |
background: hsl(0 0% 10%); | |
color: hsl(0 0% 98%); | |
} | |
@keyframes track-translate { | |
to { | |
translate: var(--destination-x, 0) var(--destination-y, 0); | |
} | |
} | |
[data-direction=horizontal] ul { | |
height: 100%; | |
width: fit-content; | |
align-items: center; | |
} | |
[data-direction=vertical] ul { | |
width: 100%; | |
height: fit-content; | |
justify-items: center; | |
flex-direction: column; | |
} | |
li { | |
height: 80%; | |
aspect-ratio: 4 / 3; | |
background: hsl(0 0% 90%); | |
border-radius: 6px; | |
font-size: clamp(2rem, 4vw + 1rem, 8rem); | |
display: grid; | |
place-items: center; | |
border: 1px solid hsl(0 0% 50%); | |
} | |
[data-play-state=running] :is(ul, li) { | |
animation-play-state: running !important; | |
} | |
[data-play-state=paused] :is(ul, li) { | |
animation-play-state: paused !important; | |
} | |
/* The animation stuff */ | |
@media(prefers-reduced-motion: no-preference) { | |
[data-translate=items] ul { | |
gap: 0; | |
} | |
[data-translate=items][data-direction=horizontal].container { | |
padding-inline: 0; | |
} | |
[data-translate=items][data-direction=vertical].container { | |
padding-block: 0; | |
} | |
[data-translate=items][data-spill=true][data-direction=horizontal].container::after { | |
--padding-x: 0rem; | |
} | |
[data-translate=items][data-direction=vertical][data-spill=true].container::after { | |
--padding-y: 0rem; | |
} | |
[data-translate=items] li { | |
--duration: calc(var(--speed) * 1s); | |
--delay: calc((var(--duration) / var(--count)) * (var(--index, 0) - (var(--count) * 0.5))); | |
animation: slide var(--duration) calc(var(--delay) - (var(--count) * 0.5s)) infinite linear paused; | |
translate: var(--origin-x) var(--origin-y); | |
} | |
[data-translate=items][data-direction=horizontal] li { | |
--origin-x: calc(((var(--count) - var(--index)) + var(--inset, 0)) * 100%); | |
--origin-y: 0; | |
--destination-x: calc(calc((var(--index) + 1 + var(--outset, 0)) * -100%)); | |
--destination-y: 0; | |
} | |
[data-translate=items][data-direction=vertical] li { | |
--origin-x: 0; | |
--origin-y: calc(((var(--count) - var(--index)) + var(--inset, 0)) * 100%); | |
--destination-x: 0; | |
--destination-y: calc(calc((var(--index) + 1 + var(--outset, 0)) * -100%)); | |
} | |
@keyframes slide { | |
100% { | |
translate: var(--destination-x) var(--destination-y); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment