Skip to content

Instantly share code, notes, and snippets.

@pbnkp
Created January 24, 2024 05:54
Show Gist options
  • Save pbnkp/5d42a827e49be3cc06d21ebe86a20c8d to your computer and use it in GitHub Desktop.
Save pbnkp/5d42a827e49be3cc06d21ebe86a20c8d to your computer and use it in GitHub Desktop.
Infinite Marquee Effect Broken Down
<div id="app"></div>
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)
@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