Merge pull request #275 from stz184/implement-custom-lazy-loading
Replace react-lazyload with custom IntersectionObserver
This commit is contained in:
commit
fcae4a89ec
File diff suppressed because it is too large
Load Diff
|
@ -13,7 +13,6 @@
|
|||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-lazyload": "^3.1.0",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-spring": "^8.0.27",
|
||||
"semantic-ui-css": "^2.4.1",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useCallback } from 'react'
|
||||
import React, { useCallback, useEffect } from 'react'
|
||||
import ReactRouterPropTypes from 'react-router-prop-types'
|
||||
import { useQuery, gql } from '@apollo/client'
|
||||
import AlbumGallery from '../../components/albumGallery/AlbumGallery'
|
||||
|
@ -7,6 +7,7 @@ import Layout from '../../Layout'
|
|||
import useURLParameters from '../../hooks/useURLParameters'
|
||||
import useScrollPagination from '../../hooks/useScrollPagination'
|
||||
import PaginateLoader from '../../components/PaginateLoader'
|
||||
import LazyLoad from '../../helpers/LazyLoad'
|
||||
|
||||
const albumQuery = gql`
|
||||
query albumQuery(
|
||||
|
@ -124,6 +125,17 @@ function AlbumPage({ match }) {
|
|||
[setOnlyFavorites, refetch]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
LazyLoad.loadImages(document.querySelectorAll('img[data-src]'))
|
||||
return () => LazyLoad.disconnect()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading) {
|
||||
LazyLoad.loadImages(document.querySelectorAll('img[data-src]'))
|
||||
}
|
||||
}, [finishedLoadingMore, onlyFavorites, loading])
|
||||
|
||||
if (error) return <div>Error</div>
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import React from 'react'
|
||||
import React, { useEffect } from 'react'
|
||||
import AlbumBoxes from '../../components/albumGallery/AlbumBoxes'
|
||||
import Layout from '../../Layout'
|
||||
import { useQuery, gql } from '@apollo/client'
|
||||
import LazyLoad from '../../helpers/LazyLoad'
|
||||
|
||||
const getAlbumsQuery = gql`
|
||||
query getMyAlbums {
|
||||
|
@ -20,6 +21,14 @@ const getAlbumsQuery = gql`
|
|||
const AlbumsPage = () => {
|
||||
const { loading, error, data } = useQuery(getAlbumsQuery)
|
||||
|
||||
useEffect(() => {
|
||||
return () => LazyLoad.disconnect()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
!loading && LazyLoad.loadImages(document.querySelectorAll('img[data-src]'))
|
||||
}, [loading])
|
||||
|
||||
return (
|
||||
<Layout title="Albums">
|
||||
<h1>Albums</h1>
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import React, { useState } from 'react'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { useMutation, gql } from '@apollo/client'
|
||||
import PropTypes from 'prop-types'
|
||||
import styled from 'styled-components'
|
||||
import LazyLoad from 'react-lazyload'
|
||||
import { Icon } from 'semantic-ui-react'
|
||||
import { ProtectedImage } from './ProtectedMedia'
|
||||
|
||||
|
@ -35,31 +34,22 @@ const StyledPhoto = styled(ProtectedImage)`
|
|||
transition: opacity 300ms;
|
||||
`
|
||||
|
||||
const PhotoImg = photoProps => {
|
||||
const LazyPhoto = photoProps => {
|
||||
const [loaded, setLoaded] = useState(false)
|
||||
const onLoad = useCallback(e => {
|
||||
!e.target.dataset.src && setLoaded(true)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<StyledPhoto
|
||||
{...photoProps}
|
||||
lazyLoading
|
||||
loaded={loaded ? 1 : 0}
|
||||
onLoad={() => {
|
||||
setLoaded(true)
|
||||
}}
|
||||
onLoad={onLoad}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const LazyPhoto = React.memo(
|
||||
props => {
|
||||
return (
|
||||
<LazyLoad scrollContainer="#layout-content">
|
||||
<PhotoImg {...props} />
|
||||
</LazyLoad>
|
||||
)
|
||||
},
|
||||
(prevProps, nextProps) => prevProps.src === nextProps.src
|
||||
)
|
||||
|
||||
LazyPhoto.propTypes = {
|
||||
src: PropTypes.string,
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useContext, useEffect } from 'react'
|
||||
import React, { useContext } from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { Loader } from 'semantic-ui-react'
|
||||
import { MediaThumbnail, PhotoThumbnail } from './MediaThumbnail'
|
||||
|
@ -6,7 +6,6 @@ import PresentView from './presentView/PresentView'
|
|||
import PropTypes from 'prop-types'
|
||||
import { SidebarContext } from '../sidebar/Sidebar'
|
||||
import MediaSidebar from '../sidebar/MediaSidebar'
|
||||
import { forceCheck } from 'react-lazyload'
|
||||
|
||||
const Gallery = styled.div`
|
||||
display: flex;
|
||||
|
@ -78,10 +77,6 @@ const PhotoGallery = ({
|
|||
return photoElements
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
!loading && forceCheck()
|
||||
}, [loading])
|
||||
|
||||
return (
|
||||
<ClearWrap>
|
||||
<Gallery>
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
const isNativeLazyLoadSupported = 'loading' in HTMLImageElement.prototype
|
||||
const placeholder = 'data:image/gif;base64,R0lGODlhAQABAPAAAAAAAAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=='
|
||||
|
||||
const getProtectedUrl = url => {
|
||||
if (url == null) return null
|
||||
|
||||
|
@ -17,18 +20,36 @@ const getProtectedUrl = url => {
|
|||
|
||||
/**
|
||||
* An image that needs authorization to load
|
||||
* Set lazyLoading to true if you want the image to be loaded once it enters the viewport
|
||||
* Native lazy load via HTMLImageElement.loading attribute will be preferred if it is supported by the browser,
|
||||
* otherwise IntersectionObserver will be used.
|
||||
*/
|
||||
export const ProtectedImage = ({ src, ...props }) => (
|
||||
<img
|
||||
key={src}
|
||||
{...props}
|
||||
src={getProtectedUrl(src)}
|
||||
crossOrigin="use-credentials"
|
||||
/>
|
||||
)
|
||||
export const ProtectedImage = ({ src, lazyLoading, ...props }) => {
|
||||
if (!isNativeLazyLoadSupported && lazyLoading) {
|
||||
props['data-src'] = getProtectedUrl(src)
|
||||
}
|
||||
|
||||
if (isNativeLazyLoadSupported && lazyLoading) {
|
||||
props.loading = 'lazy'
|
||||
}
|
||||
|
||||
return (
|
||||
<img
|
||||
key={src}
|
||||
{...props}
|
||||
src={
|
||||
lazyLoading && !isNativeLazyLoadSupported
|
||||
? placeholder
|
||||
: getProtectedUrl(src)
|
||||
}
|
||||
crossOrigin="use-credentials"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
ProtectedImage.propTypes = {
|
||||
src: PropTypes.string,
|
||||
lazyLoading: PropTypes.bool,
|
||||
}
|
||||
|
||||
export const ProtectedVideo = ({ media, ...props }) => (
|
||||
|
|
|
@ -9,6 +9,7 @@ import useURLParameters from '../../hooks/useURLParameters'
|
|||
import { FavoritesCheckbox } from '../AlbumFilter'
|
||||
import useScrollPagination from '../../hooks/useScrollPagination'
|
||||
import PaginateLoader from '../PaginateLoader'
|
||||
import LazyLoad from '../../helpers/LazyLoad'
|
||||
|
||||
const MY_TIMELINE_QUERY = gql`
|
||||
query myTimeline($onlyFavorites: Boolean, $limit: Int, $offset: Int) {
|
||||
|
@ -156,6 +157,14 @@ const TimelineGallery = () => {
|
|||
}
|
||||
}, [onlyFavorites])
|
||||
|
||||
useEffect(() => {
|
||||
!loading && LazyLoad.loadImages(document.querySelectorAll('img[data-src]'))
|
||||
}, [finishedLoadingMore, onlyFavorites, loading])
|
||||
|
||||
useEffect(() => {
|
||||
return () => LazyLoad.disconnect()
|
||||
}, [])
|
||||
|
||||
if (error) {
|
||||
return error
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
class LazyLoad {
|
||||
constructor() {
|
||||
this.observe = this.observe.bind(this)
|
||||
this.loadImages = this.loadImages.bind(this)
|
||||
this.disconnect = this.disconnect.bind(this)
|
||||
this.observer = null
|
||||
}
|
||||
|
||||
observe(images) {
|
||||
if (!this.observer) {
|
||||
this.observer = new IntersectionObserver(entries => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting || entry.intersectionRatio > 0) {
|
||||
const element = entry.target
|
||||
this.setSrcAttribute(element)
|
||||
this.observer.unobserve(element)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
Array.from(images).forEach(image => this.observer.observe(image))
|
||||
}
|
||||
|
||||
loadImages(elements) {
|
||||
const images = Array.from(elements)
|
||||
if (images.length) {
|
||||
if ('IntersectionObserver' in window) {
|
||||
this.observe(images)
|
||||
} else {
|
||||
images.forEach(image => this.setSrcAttribute(image))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.observer && this.observer.disconnect()
|
||||
}
|
||||
|
||||
setSrcAttribute(element) {
|
||||
if (element.hasAttribute('data-src')) {
|
||||
const src = element.getAttribute('data-src')
|
||||
element.removeAttribute('data-src')
|
||||
element.setAttribute('src', src)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new LazyLoad()
|
Loading…
Reference in New Issue