Skip to content

Instantly share code, notes, and snippets.

@tresorama
Last active April 27, 2023 09:40
Show Gist options
  • Save tresorama/2a43035c0882ef5ef6cabe488941e2ac to your computer and use it in GitHub Desktop.
Save tresorama/2a43035c0882ef5ef6cabe488941e2ac to your computer and use it in GitHub Desktop.
react-marquee
/* eslint-disable @next/next/no-img-element */
import React, { useRef } from 'react';
import cx from 'classnames';
import useIntersectionObserver from './hooks/use-intersection-observer';
type MarqueeProps = {
imagesUrls: string[],
};
export const ImagesSlider_WithPlainCSS = ({ imagesUrls }: MarqueeProps) => {
const ref = useRef<HTMLDivElement | null>(null);
const entry = useIntersectionObserver(ref, { rootMargin: "100px 0px" });
const isInViewport = !!entry?.isIntersecting;
return (
<>
<style jsx global>{`
@keyframes scroll-slider {
0% { transform: translate3d(0, 0, 0); }
100% { transform: translate3d(-33.33%, 0, 0); }
}
.ImagesSlider--animate {
--duration: calc(var(--items-count, 10) * 4s);
animation: scroll-slider var(--duration) linear infinite;
transform-origin: left;
}
`}</style>
<div ref={ref} className={cx("h-96 w-full", { "invisible": !isInViewport })}>
<div
className='h-full w-max flex ImagesSlider--animate'
style={{
"--items-count": imagesUrls.length,
animationPlayState: isInViewport ? 'running' : 'paused',
} as React.CSSProperties}
>
{[...imagesUrls, ...imagesUrls, ...imagesUrls].map((imageUrl, index) => (
<img className='h-full pointer-events-none' key={imageUrl + index} src={imageUrl} alt={"" + index} loading="eager" />
))}
</div>
</div>
</>
);
};
/* eslint-disable @next/next/no-img-element */
import React from 'react';
type MarqueeProps = {
imagesUrls: string[],
};
export const ImagesSlider_PlainCSS_V2 = ({ imagesUrls }: MarqueeProps) => {
const style = {
"--items-count": imagesUrls.length,
} as React.CSSProperties;
return (
<>
<style jsx global>{`
@keyframes ImagesSlider_PlainCSS_V2_scroll {
from { transform: translateX(0%); }
to { transform: translateX(-100%); }
}
.ImagesSlider_PlainCSS_V2--animate {
--duration: calc(var(--items-count) * 2s);
animation: ImagesSlider_PlainCSS_V2_scroll var(--duration) linear 0s infinite;
animation-play-state: running;
}
`}</style>
<div className='overflow-x-hidden flex relative w-full transform-none' style={style}>
{new Array(2).fill('').map((_, i) => (
<div key={i} className='min-w-full flex-[0_0_auto] z-[1] flex items-center ImagesSlider_PlainCSS_V2--animate'>
<div className="flex-[0_0_auto] flex min-w-[auto]">
{imagesUrls.map((imageUrl, index) => (
<div key={imageUrl + "-" + index} className="transform-none">
<img
className='h-96 object-cover'
src={imageUrl}
alt={"" + index}
/>
</div>
))}
</div>
</div>
))}
</div>
</>
);
};
/* eslint-disable @next/next/no-img-element */
import React from 'react';
import Marquee_RFM from "react-fast-marquee";
type MarqueeProps = {
imagesUrls: string[],
};
export const ImagesSlider_WithReactFastMarquee = ({ imagesUrls }: MarqueeProps) => {
return (
<Marquee_RFM speed={80}>
{[...imagesUrls].map((imageUrl, index) => (
<img
key={imageUrl + "-" + index}
className='h-96 object-cover'
src={imageUrl}
alt={"" + index}
/>
))}
</Marquee_RFM>
);
};
/* eslint-disable @next/next/no-img-element */
import React from 'react';
import { Swiper, SwiperSlide } from 'swiper/react';
import { Autoplay } from 'swiper';
import 'swiper/css';
import 'swiper/css/autoplay';
type MarqueeProps = {
imagesUrls: string[],
};
export const ImagesSlider_WithSwiper = ({ imagesUrls }: MarqueeProps) => {
return (
<>
<style jsx global>{`
.ImagesSlider_WithSwiper .swiper-wrapper {
// transition-timing-function: linear;
}
.ImagesSlider_WithSwiper .swiper-slide {
width: auto;
height: 24rem;
}
`}</style>
<Swiper
className='ImagesSlider_WithSwiper'
modules={[Autoplay]}
speed={3900}
loop
autoplay={{ disableOnInteraction: false, delay: 0 }}
// slidesPerView={4}
slidesPerView={'auto'}
spaceBetween={0}
allowTouchMove={false}
>
{[...imagesUrls, ...imagesUrls].map((imageUrl, index) => (
<SwiperSlide key={imageUrl + "-" + index}>
<img
className='h-full'
src={imageUrl}
alt={"" + index}
/>
</SwiperSlide>
))}
</Swiper>
</>
);
};
import { useRef } from 'react';
import { useIntersectionObserver } from './use-intersection-observer';
const Section = (props: { title: string; }) => {
const ref = useRef<HTMLDivElement | null>(null);
const entry = useIntersectionObserver(ref, {});
const isVisible = !!entry?.isIntersecting;
console.log(`Render Section ${props.title}`, { isVisible });
return (
<div
ref={ref}
style={{
minHeight: '100vh',
display: 'flex',
border: '1px dashed #000',
fontSize: '2rem',
}}
>
<div style={{ margin: 'auto' }}>{props.title}</div>
</div>
);
};
export default function Component() {
return (
<>
{Array.from({ length: 5 }).map((_, index) => (
<Section key={index + 1} title={`${index + 1}`} />
))}
</>
);
}
// credit: https://usehooks-ts.com/react-hook/use-intersection-observer
import { RefObject, useEffect, useState } from 'react';
interface Args extends IntersectionObserverInit {
freezeOnceVisible?: boolean;
}
export function useIntersectionObserver(
elementRef: RefObject<Element>,
{
threshold = 0,
root = null,
rootMargin = '0%',
freezeOnceVisible = false,
}: Args,
): IntersectionObserverEntry | undefined {
const [entry, setEntry] = useState<IntersectionObserverEntry>();
const frozen = entry?.isIntersecting && freezeOnceVisible;
const updateEntry = ([entry]: IntersectionObserverEntry[]): void => {
setEntry(entry);
};
useEffect(() => {
const node = elementRef?.current; // DOM Ref
const hasIOSupport = !!window.IntersectionObserver;
if (!hasIOSupport || frozen || !node) return;
const observerParams = { threshold, root, rootMargin };
const observer = new IntersectionObserver(updateEntry, observerParams);
observer.observe(node);
return () => observer.disconnect();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [elementRef?.current, JSON.stringify(threshold), root, rootMargin, frozen]);
return entry;
}
export default useIntersectionObserver;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment