Open Skylsmoi opened 5 years ago
Possible workaround is a custom View renderer. But this solution using much more traffic in case of reverse scrolling. E.g.
const ViewRenderer = (props) => {
const overScanCount = 1;
const {
data, getStyles, index, currentIndex,
} = props;
const { alt, src } = data;
return Math.abs(currentIndex - index) <= overScanCount ? (
<div style={getStyles('view', props)}>
<img
alt={alt || `Image ${index}`}
src={src}
style={{
height: 'auto',
maxHeight: '100vh',
maxWidth: '100%',
userSelect: 'none',
}}
/>
</div>
) : null;
};
<Carousel
views={images}
components={{ View: ViewRenderer }}
/>
Mmh interesting solution.
I personally have tried to give only a subset of the image list to Carousel and update that subset on click on next and previous buttons.
It worked but updating the subset list on previous/next click removed the swipe left/right animations which wasn't great.
By the way, react-images uses react-view-pager as dependency which has a lazyLoad option tagged as "Comming soon". So we might have to wait for it to be implemented
I solved this in a similar way but wrapped the base View
and set the source to render a transparent pixel instead of the actual img url.
import React from 'react'
import {carouselComponents} from 'react-images'
const BaseView = carouselComponents.View
// transparent pixel: http://png-pixel.com/
const pixel = {source: 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=='}
const overscan = 3
export default function View (props) {
const {currentIndex, index, data} = props
const inBounds = Math.abs(currentIndex - index) <= overscan
return <BaseView {...props} data={inBounds ? data : pixel} />
}
I did notice the animation is affected though.
Doing something with a delay helps the animations keep going:
const overscan = 3;
const animationTimeout = 1000; //ms
const nextInBounds = Math.abs(currentIndex - index) <= overscan;
const [inBounds, setInBounds] = useState(nextInBounds);
useEffect(() => {
if (currentIndex === index) {
// got behind, so do it now
setInBounds(true);
return;
}
if (nextInBounds === inBounds) return;
const timer = setTimeout(() => setInBounds(nextInBounds), animationTimeout);
return () => {
clearTimeout(timer);
};
}, [inBounds, nextInBounds, currentIndex, index]);
Edit: Even better is using an SVG placeholder, then no animations are affected by loading, and loading is not delayed.
import React from "react";
import { Div, Img } from "react-images/lib/primitives";
import { className } from "react-images/lib/utils";
import { getSource } from "react-images/lib/components/component-helpers";
function svgPlaceholder(width, height) {
return `data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}"%3E%3C/svg%3E`;
}
export function CustomView(props) {
const {
data,
formatters,
getStyles,
index,
currentIndex,
isFullscreen,
isModal
} = props;
const overscan = 3;
const inBounds = Math.abs(currentIndex - index) <= overscan;
const innerProps = {
alt: formatters.getAltText({ data, index }),
src: inBounds
? getSource({ data, isFullscreen })
: svgPlaceholder(data.width, data.height)
};
return (
<Div
css={getStyles("view", props)}
className={className("view", { isFullscreen, isModal })}
>
<Img
{...innerProps}
className={className("view-image", { isFullscreen, isModal })}
css={{
height: "auto",
maxHeight: "100vh",
maxWidth: "100vw",
userSelect: "none"
}}
/>
</Div>
);
}
On such implementation
When
modalIsOpen
istrue
, the modal opens and starts downloading every elements ofimages
.If there is a lot of elements, it slows down the browser a lot, make the animation feeze and cost a lot of memory, especially on mobile.
Is there a way to disable the animation and download only the visible image or maybe lazy load only the few next images ?