diff --git a/ui/package.json b/ui/package.json
index e4ff9a9b..0d823f10 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -54,7 +54,7 @@
"url-join": "^4.0.1"
},
"scripts": {
- "start": "BROWSER=none craco start",
+ "start": "BROWSER=none PORT=1234 craco start",
"build": "craco build",
"test": "npm run lint && npm run jest -- --watchAll=false",
"test:ci": "npm run lint && npm run jest:ci",
diff --git a/ui/src/Pages/AlbumPage/AlbumPage.tsx b/ui/src/Pages/AlbumPage/AlbumPage.tsx
index b7b141c5..5877e437 100644
--- a/ui/src/Pages/AlbumPage/AlbumPage.tsx
+++ b/ui/src/Pages/AlbumPage/AlbumPage.tsx
@@ -1,11 +1,10 @@
-import React, { useCallback, useEffect } from 'react'
+import React, { useCallback } from 'react'
import { useQuery, gql } from '@apollo/client'
import AlbumGallery from '../../components/albumGallery/AlbumGallery'
import Layout from '../../components/layout/Layout'
import useURLParameters from '../../hooks/useURLParameters'
import useScrollPagination from '../../hooks/useScrollPagination'
import PaginateLoader from '../../components/PaginateLoader'
-import LazyLoad from '../../helpers/LazyLoad'
import { useTranslation } from 'react-i18next'
import { albumQuery, albumQueryVariables } from './__generated__/albumQuery'
import useOrderingParams from '../../hooks/useOrderingParams'
@@ -125,17 +124,6 @@ function AlbumPage({ match }: AlbumPageProps) {
[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
Error
return (
diff --git a/ui/src/Pages/AllAlbumsPage/AlbumsPage.tsx b/ui/src/Pages/AllAlbumsPage/AlbumsPage.tsx
index 814bbdcf..01978924 100644
--- a/ui/src/Pages/AllAlbumsPage/AlbumsPage.tsx
+++ b/ui/src/Pages/AllAlbumsPage/AlbumsPage.tsx
@@ -1,8 +1,7 @@
-import React, { useEffect } from 'react'
+import React from 'react'
import AlbumBoxes from '../../components/albumGallery/AlbumBoxes'
import Layout from '../../components/layout/Layout'
import { useQuery, gql } from '@apollo/client'
-import LazyLoad from '../../helpers/LazyLoad'
import { getMyAlbums } from './__generated__/getMyAlbums'
const getAlbumsQuery = gql`
@@ -20,15 +19,7 @@ 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])
+ const { error, data } = useQuery(getAlbumsQuery)
return (
diff --git a/ui/src/components/photoGallery/MediaThumbnail.tsx b/ui/src/components/photoGallery/MediaThumbnail.tsx
index eb97d662..e9df158d 100644
--- a/ui/src/components/photoGallery/MediaThumbnail.tsx
+++ b/ui/src/components/photoGallery/MediaThumbnail.tsx
@@ -1,4 +1,4 @@
-import React, { useCallback, useState } from 'react'
+import React from 'react'
import styled from 'styled-components'
import { ProtectedImage } from './ProtectedMedia'
import { MediaType } from '../../__generated__/globalTypes'
@@ -14,12 +14,11 @@ const MediaContainer = styled.div`
overflow: hidden;
`
-const StyledPhoto = styled(ProtectedImage)<{ loaded: boolean }>`
+const StyledPhoto = styled(ProtectedImage)`
height: 200px;
min-width: 100%;
position: relative;
object-fit: cover;
- opacity: ${({ loaded }) => (loaded ? 1 : 0)};
transition: opacity 300ms;
`
@@ -29,14 +28,7 @@ type LazyPhotoProps = {
}
const LazyPhoto = (photoProps: LazyPhotoProps) => {
- const [loaded, setLoaded] = useState(false)
- const onLoad = useCallback(e => {
- !e.target.dataset.src && setLoaded(true)
- }, [])
-
- return (
-
- )
+ return
}
const PhotoOverlay = styled.div<{ active: boolean }>`
diff --git a/ui/src/components/photoGallery/ProtectedMedia.tsx b/ui/src/components/photoGallery/ProtectedMedia.tsx
index bd95cfc9..e77da326 100644
--- a/ui/src/components/photoGallery/ProtectedMedia.tsx
+++ b/ui/src/components/photoGallery/ProtectedMedia.tsx
@@ -1,7 +1,11 @@
+import classNames from 'classnames'
import React, { DetailedHTMLProps, ImgHTMLAttributes } from 'react'
+import { useRef } from 'react'
+import { useState } from 'react'
+import { useEffect } from 'react'
import { isNil } from '../../helpers/utils'
-const isNativeLazyLoadSupported = 'loading' in HTMLImageElement.prototype
+const isNativeLazyLoadSupported = 'loading' in document.createElement('img')
const placeholder =
'data:image/gif;base64,R0lGODlhAQABAPAAAAAAAAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=='
@@ -27,7 +31,6 @@ export interface ProtectedImageProps
src?: string
key?: string
lazyLoading?: boolean
- loaded?: boolean
}
/**
@@ -38,41 +41,83 @@ export interface ProtectedImageProps
*/
export const ProtectedImage = ({
src,
- key,
lazyLoading,
- loaded,
...props
}: ProtectedImageProps) => {
- const lazyLoadProps: { 'data-src'?: string; loading?: 'lazy' | 'eager' } = {}
+ const url = getProtectedUrl(src) || placeholder
- if (!isNativeLazyLoadSupported && lazyLoading) {
- lazyLoadProps['data-src'] = getProtectedUrl(src)
+ if (!lazyLoading) {
+ return (
+
+ )
}
- if (isNativeLazyLoadSupported && lazyLoading) {
- lazyLoadProps.loading = 'lazy'
+ if (!isNativeLazyLoadSupported) {
+ return
}
- const imgSrc: string =
- lazyLoading && !isNativeLazyLoadSupported
- ? placeholder
- : getProtectedUrl(src) || placeholder
-
- const loadedProp =
- loaded !== undefined ? { loaded: loaded.toString() } : undefined
-
+ // load with native lazy loading
return (
-
+
)
}
+interface FallbackLazyloadedImageProps
+ extends Omit<
+ DetailedHTMLProps, HTMLImageElement>,
+ 'src'
+ > {
+ src?: string
+}
+
+const FallbackLazyloadedImage = ({
+ src,
+ ...props
+}: FallbackLazyloadedImageProps) => {
+ const [inView, setInView] = useState(false)
+ const imgRef = useRef(null)
+
+ useEffect(() => {
+ const imgElm = imgRef.current
+ if (isNil(imgElm) || inView) return
+
+ if (window.IntersectionObserver === undefined) {
+ setInView(true)
+ return
+ }
+
+ const observer = new IntersectionObserver(
+ ([entry]) => {
+ if (entry.isIntersecting) {
+ setInView(true)
+ observer.disconnect()
+ }
+ },
+ {
+ root: null,
+ threshold: 0,
+ }
+ )
+
+ observer.observe(imgElm)
+
+ return () => {
+ observer.disconnect()
+ }
+ }, [imgRef])
+
+ if (inView) {
+ return
+ } else {
+ return (
+
+ )
+ }
+}
+
export interface ProtectedVideoProps_Media {
__typename: 'Media'
id: string
diff --git a/ui/src/components/timelineGallery/TimelineGallery.tsx b/ui/src/components/timelineGallery/TimelineGallery.tsx
index eb7c5ffd..81e29125 100644
--- a/ui/src/components/timelineGallery/TimelineGallery.tsx
+++ b/ui/src/components/timelineGallery/TimelineGallery.tsx
@@ -7,7 +7,6 @@ import useURLParameters from '../../hooks/useURLParameters'
import { FavoritesCheckbox } from '../album/AlbumFilter'
import useScrollPagination from '../../hooks/useScrollPagination'
import PaginateLoader from '../PaginateLoader'
-import LazyLoad from '../../helpers/LazyLoad'
import { useTranslation } from 'react-i18next'
import {
myTimeline,
@@ -141,14 +140,6 @@ const TimelineGallery = () => {
})
}, [onlyFavorites])
- useEffect(() => {
- !loading && LazyLoad.loadImages(document.querySelectorAll('img[data-src]'))
- }, [finishedLoadingMore, onlyFavorites, loading])
-
- useEffect(() => {
- return () => LazyLoad.disconnect()
- }, [])
-
if (error) {
return {error.message}
}
diff --git a/ui/src/helpers/LazyLoad.ts b/ui/src/helpers/LazyLoad.ts
deleted file mode 100644
index c071bd1c..00000000
--- a/ui/src/helpers/LazyLoad.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-class LazyLoad {
- observer: null | IntersectionObserver
-
- constructor() {
- this.observe = this.observe.bind(this)
- this.loadImages = this.loadImages.bind(this)
- this.disconnect = this.disconnect.bind(this)
- this.observer = null
- }
-
- observe(images: Element[]) {
- 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: NodeListOf) {
- 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: Element) {
- if (element.hasAttribute('data-src')) {
- const src = element.getAttribute('data-src')
- if (src) {
- element.removeAttribute('data-src')
- element.setAttribute('src', src)
- } else {
- console.warn(
- 'WARN: expected element to have `data-src` property',
- element
- )
- }
- }
- }
-}
-
-export default new LazyLoad()