Skip to content

Instantly share code, notes, and snippets.

@eseQ
Created September 26, 2022 10:34
Show Gist options
  • Save eseQ/9f4c93c88ee2b89eaef385800886b460 to your computer and use it in GitHub Desktop.
Save eseQ/9f4c93c88ee2b89eaef385800886b460 to your computer and use it in GitHub Desktop.
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable max-len */
import { useCallback, useEffect, useRef, useState } from 'react';
import Image from 'next/image';
import { useTranslation } from 'next-i18next';
import { useIsMounted } from '@utils';
import useMeasure from 'react-use/lib/useMeasure';
import One from '@images/landing/examples/1.png';
import Two from '@images/landing/examples/2.png';
import Three from '@images/landing/examples/3.png';
import Four from '@images/landing/examples/4.png';
import Five from '@images/landing/examples/5.png';
import Six from '@images/landing/examples/6.png';
import Seven from '@images/landing/examples/7.png';
import Eight from '@images/landing/examples/8.png';
import Nine from '@images/landing/examples/9.png';
import Section from '../elements/Section';
const data = [
[One, Two, Three],
[Four, Five, Six],
[Seven, Eight, Nine],
[], // hack to make the last column workable on safari
].map((d) => d.map((i) => ({ name: i.src.match(/(?!.*\/)(.*)$/)?.[0] || '', image: i })));
type Element = { name: string; image: StaticImageData };
type ColumnProps = {
list: Element[];
toTop?: boolean;
horizontal?: boolean;
id?: string;
};
const duration = 50000;
const margin = 24;
function Column({ list, toTop = false, id: columnId = 'default', horizontal }: ColumnProps) {
const makeEntries = useCallback((array: Element[]) => {
const elements = [...array].map((d) => ({ ...d, name: `${d.name}-cloned` }));
let result = [...elements, ...array];
if (!toTop) result = [...array, ...elements];
return result.reduce((r, d) => ({ ...r, [d.name]: d }), {}) as Record<string, Element>;
}, [toTop]);
const [entries] = useState(() => makeEntries(list));
const order = useRef(Object.keys(entries));
const [ref, { width: boxWidth, height: boxHeight }] = useMeasure<HTMLDivElement>();
const getDistance = useCallback((id: string) => {
const { height, width } = entries[id]?.image || {};
if (horizontal) return (boxHeight / height) * width;
return (boxWidth / width) * height;
}, [boxWidth, boxHeight, entries, horizontal]);
const getOffsets = useCallback(() => {
let array = [...order.current];
if (toTop) array = array.reverse();
return array.reduce((r, d) => {
const prevId = array[array.indexOf(d) - 1];
return {
...r,
[d]: (prevId ? (getDistance(prevId) + r[prevId]) : 0) + margin,
};
}, {} as Record<string, number>);
}, [getDistance, toTop]);
const [offsets, setOffsets] = useState(getOffsets);
useEffect(() => { setOffsets(getOffsets()); }, [getOffsets]);
const startElement = order.current[toTop ? 0 : order.current.length - 1];
const distance = Math.round(Math.max(...Object.values(offsets)) + getDistance(startElement));
let position = 'top';
if (!horizontal && toTop) position = 'bottom';
if (horizontal && toTop) position = 'right';
if (horizontal && !toTop) position = 'left';
return (
<>
<style>
{`
@keyframes flight-${columnId} {
0% { transform: translate${horizontal ? 'X' : 'Y'}(0) translateZ(0); }
100% { transform: translate${horizontal ? 'X' : 'Y'}(${distance * (toTop ? -1 : 1)}px) translateZ(0); }
}
`}
</style>
<div
ref={ref}
className="w-full md:w-[32%] h-[31%] md:h-full relative"
style={{ ...(Object.keys(entries).length > 0 ? {} : { width: 0, position: 'absolute' }) }}
>
<div
className={['relative', horizontal ? 'left-1/2 -translate-x-1/2' : 'top-1/2 -translate-y-1/2'].filter(Boolean).join(' ')}
style={{
[horizontal ? 'width' : 'height']: Number.isInteger(distance) ? distance : 0,
[horizontal ? 'height' : 'width']: '100%',
}}
>
{boxWidth > 0 && Object.keys(entries).map((id) => (
<div
key={entries[id].name}
className={['absolute', horizontal ? 'h-full top-0' : 'w-full left-0'].filter(Boolean).join(' ')}
style={{
height : Math.round(horizontal ? boxHeight : (boxWidth / entries[id].image.width) * entries[id].image.height),
width : Math.round(horizontal ? (boxHeight / entries[id].image.height) * entries[id].image.width : boxWidth),
[position]: 0,
animation : `flight-${columnId} ${duration}ms linear -${Math.round(offsets[id] / (distance / duration))}ms infinite`,
}}
>
<Image
src={entries[id].image}
placeholder="blur"
layout="responsive"
/>
</div>
))}
</div>
</div>
</>
);
}
function Content({ horizontal }: { horizontal?: boolean }) {
const isMounted = useIsMounted();
if (!isMounted) return null;
return (
<>
{data.map((col, index) => (
<Column
key={col.map((d) => d.name).join('-')}
id={col.map((d) => d.name).join('-').replace(/\./g, '')}
list={col}
toTop={index % 2 === (horizontal ? 1 : 0)}
horizontal={horizontal}
/>
))}
</>
);
}
type Props = { className?: string; horizontal?: boolean; }
function Examples({ className, horizontal }: Props) {
const { t } = useTranslation('landing');
return (
<Section className={['', className].filter(Boolean).join(' ')}>
<h2 className="text-center mx-6 md:mx-0">
{t('examples.title')}
</h2>
<div className="mt-8 w-full relative pb-[110%] md:pb-[56.25%] overflow-hidden">
<div className="absolute left-0 top-0 h-full w-full flex flex-col md:flex-row justify-between">
<Content horizontal={horizontal} />
</div>
</div>
</Section>
);
}
export default Examples;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment