From acc18259e82765e75801eb7558122d2c0e8ddf2d Mon Sep 17 00:00:00 2001 From: Marvin Heilemann Date: Tue, 3 Mar 2020 22:52:22 +0100 Subject: [PATCH] feat: improved lightbox fixed lightbox no longer opening better closing handling improved overlay and added events revoked pointer-events restriction to important parts Closes: #15 --- package.json | 3 +- pnpm-lock.yaml | 6 - src/components/Lightbox.jsx | 159 ++++++++++++++------------- src/hooks/use-overlay.js | 6 +- src/provider/overlay.js | 12 +- src/styles/_reset.scss | 1 - src/styles/components/_lightbox.scss | 17 +-- src/styles/components/_overlay.scss | 4 +- src/styles/elements/_code.scss | 1 - src/styles/layouts/_header.scss | 6 - 10 files changed, 108 insertions(+), 107 deletions(-) diff --git a/package.json b/package.json index 5264ba3..32c7e1c 100644 --- a/package.json +++ b/package.json @@ -91,8 +91,7 @@ "react-scroll": "^1.7.16", "rehype-accessible-emojis": "^0.3.2", "titleize": "^2.1.0", - "unique-string": "^2.0.0", - "vanilla-click-outside": "^2.0.0" + "unique-string": "^2.0.0" }, "devDependencies": { "@arkweid/lefthook": "^0.7.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0615604..555ba68 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -56,7 +56,6 @@ dependencies: rehype-accessible-emojis: 0.3.2 titleize: 2.1.0 unique-string: 2.0.0 - vanilla-click-outside: 2.0.0 devDependencies: '@arkweid/lefthook': 0.7.1 '@commitlint/cli': 8.3.5 @@ -17397,10 +17396,6 @@ packages: spdx-expression-parse: 3.0.0 resolution: integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== - /vanilla-click-outside/2.0.0: - dev: false - resolution: - integrity: sha512-yLn84o0gRSSWlHk7JnlCRO4YLyK556LlaZRsK5Ur87RdejbNqZ0UXtk8XCjW/y4T3BgNkubQGj+yxRwwEAWtAA== /vary/1.1.2: dev: false engines: @@ -18354,4 +18349,3 @@ specifiers: taskz: ^1.3.0 titleize: ^2.1.0 unique-string: ^2.0.0 - vanilla-click-outside: ^2.0.0 diff --git a/src/components/Lightbox.jsx b/src/components/Lightbox.jsx index c566881..6ed73ed 100644 --- a/src/components/Lightbox.jsx +++ b/src/components/Lightbox.jsx @@ -1,24 +1,15 @@ -import React, { createRef } from 'react' +import React, { useState, useRef, useEffect } from 'react' -import { OverlayContext } from '../provider/overlay' +import { useOverlay } from '../hooks/use-overlay' -// TODO: expose lightbox state so other component could eventually hide the lightbox -class Lightbox extends React.Component { - static contextType = OverlayContext +const Lightbox = ({ children, style: globalStyle }) => { + const elementRef = useRef(null) + const [visible, setVisibility] = useState(false) + const overlay = useOverlay() - state = { - open: false, - } - - constructor(props) { - super(props) - - this.elementRef = createRef(null) - } - - zoomIn = () => { - const element = this.elementRef.current - const link = element.querySelector('.gatsby-resp-image-link') + const zoomIn = () => { + const element = elementRef.current + const wrapper = element.querySelector('.resp-image-wrapper') const image = element.querySelector('picture img') const clientRect = element.getBoundingClientRect() @@ -28,7 +19,10 @@ class Lightbox extends React.Component { const clientY = element.offsetTop - document.documentElement.scrollTop const maxWidth = document.documentElement.clientWidth const maxHeight = document.documentElement.clientHeight + + // Adjust const scrollbarWidth = 0 + const margin = 50 // Get the current picture source image natural width. const naturalWidth = image.naturalWidth @@ -45,23 +39,23 @@ class Lightbox extends React.Component { if (naturalHeight > clientHeight) { if (clientHeight === clientWidth && maxHeight > maxWidth) { // case 1: square image and screen h > w - scale = (maxWidth - this.margin) / clientWidth + scale = (maxWidth - margin) / clientWidth } else if (clientHeight === clientWidth && maxHeight < maxWidth) { // case 2: square image and screen w > h - scale = (maxHeight - this.margin) / clientHeight + scale = (maxHeight - margin) / clientHeight } else if (clientHeight > clientWidth) { // case 3: rectangular image h > w - scale = (maxHeight - this.margin) / clientHeight + scale = (maxHeight - margin) / clientHeight if (scale * clientWidth > maxWidth) { // case 3b: rectangular image h > w but zoomed image is too big - scale = (maxWidth - this.margin) / clientWidth + scale = (maxWidth - margin) / clientWidth } } else if (clientHeight < clientWidth) { // case 4: rectangular image w > h - scale = (maxWidth - this.margin) / clientWidth + scale = (maxWidth - margin) / clientWidth if (scale * clientHeight > maxHeight) { // case 4b: rectangular image w > h but zoomed image is too big - scale = (maxHeight - this.margin) / clientHeight + scale = (maxHeight - margin) / clientHeight } } } @@ -69,81 +63,92 @@ class Lightbox extends React.Component { scale = naturalWidth / clientWidth } - // TODO: reduced motion, no animation - // TODO: use react animate library here + // TODO: reduced motion => no animation + // TODO: use react spring element.style.height = `${clientHeight}px` - link.classList.add('open') - link.style.height = `${clientHeight}px` - link.style.width = `${clientWidth}px` - link.style.transform = `translate(${newX}px,${newY}px)` + element.style.zIndex = 21 + wrapper.style.position = 'absolute' + wrapper.style.height = `${clientHeight}px` + wrapper.style.width = `${clientWidth}px` + wrapper.style.transform = `translate(${newX}px,${newY}px)` image.style.transform = `scale(${scale})` } - zoomOut = () => { - const element = this.elementRef.current - const link = element.querySelector('.gatsby-resp-image-link') + const zoomOut = () => { + const element = elementRef.current + const wrapper = element.querySelector('.resp-image-wrapper') const image = element.querySelector('picture img') image.style.transform = `scale(1)` - link.style.transform = `translate(0,0)` - link.style.height = `auto` - link.style.width = `auto` - link.classList.remove('open') + wrapper.style.transform = `translate(0,0)` + wrapper.style.height = `auto` + wrapper.style.width = `auto` + wrapper.style.position = 'relative' element.style.height = `auto` + + setTimeout(() => { + element.style.zIndex = 'auto' + }, 200) } - open = () => { - this.setState({ open: true }) - this.zoomIn() - this.context.show() - window.addEventListener('scroll', this.handleScroll) + const open = () => { + setVisibility(true) + zoomIn() + overlay.show() + window.addEventListener('scroll', handleScroll) } - close = () => { - this.setState({ open: false }) - window.removeEventListener('scroll', this.handleScroll) - this.context.hide() - this.zoomOut() + const close = () => { + setVisibility(false) + window.removeEventListener('scroll', handleScroll) + overlay.hide() + zoomOut() } - handleScroll = () => { - this.close() + const handleScroll = () => { + close() } - onClick = ($event) => { + const onClick = ($event) => { $event.preventDefault() - if (this.state.open) { - this.close() + if (visible) { + close() } else { - this.open() + open() + overlay.onClick(() => { + close() + }) } } - render() { - const { children, style: globalStyle } = this.props - - const wrapper = children[1] - const figcaption = children[3] - - return ( -
-
- {wrapper.props.children} -
-
- {figcaption.props.children} -
-
- ) - } + useEffect(() => { + return () => { + overlay.hide() // if something breaks hide the overlay + } + }, [elementRef]) + + const wrapper = children[1].props // yes, this is the link now + const link = wrapper.children[1].props // yes, this is the wrapper now + const figcaption = children[3].props // just the figcaption + + return ( +
+
+
{link.children}
+
+
{figcaption.children}
+
+ ) } export default Lightbox diff --git a/src/hooks/use-overlay.js b/src/hooks/use-overlay.js index d7556cf..ee174e9 100644 --- a/src/hooks/use-overlay.js +++ b/src/hooks/use-overlay.js @@ -3,9 +3,11 @@ import { useContext } from 'react' import { OverlayContext } from '../provider/overlay' function useOverlay() { - const { show, hide } = useContext(OverlayContext) + const { show, hide, on } = useContext(OverlayContext) - return { show, hide } + const onClick = (fn) => on({ click: fn }) + + return { show, hide, onClick } } export { useOverlay } diff --git a/src/provider/overlay.js b/src/provider/overlay.js index 036e90b..c595dd2 100644 --- a/src/provider/overlay.js +++ b/src/provider/overlay.js @@ -1,11 +1,14 @@ import React, { createContext, useState, useRef } from 'react' -const OverlayContext = createContext(false) +const OverlayContext = createContext() const OverlayConsumer = OverlayContext.Consumer function OverlayProvider({ children }) { const elementRef = useRef() const [visible, setVisibility] = useState(false) + const [events, on] = useState({ + click: () => {}, + }) const show = () => { setVisibility(true) @@ -15,14 +18,17 @@ function OverlayProvider({ children }) { setVisibility(false) } - // TODO: add animation and make transition a global constant + // TODO: use react spring + // - expose on animation start and end return ( - +
events.click(event)} + role="presentation" >
{children} diff --git a/src/styles/_reset.scss b/src/styles/_reset.scss index 6efdc76..23d9708 100644 --- a/src/styles/_reset.scss +++ b/src/styles/_reset.scss @@ -115,7 +115,6 @@ img, video { max-width: 100%; height: auto; - pointer-events: none; user-select: none; object-fit: cover; } diff --git a/src/styles/components/_lightbox.scss b/src/styles/components/_lightbox.scss index 1589058..d222976 100644 --- a/src/styles/components/_lightbox.scss +++ b/src/styles/components/_lightbox.scss @@ -1,13 +1,16 @@ .lightbox { - .gatsby-resp-image-link { - &.open { - position: absolute; - // TODO: make z-index SASS variables - z-index: 21; // one higher than overlay - } + user-select: none; + + .resp-image-link { + will-change: height; + cursor: pointer; + } + + .resp-image-wrapper { + will-change: width, height, transform; } - .gatsby-resp-image-link, + .resp-image-wrapper, .gatsby-resp-image-image { will-change: transform; transition: var(--transition-fast); diff --git a/src/styles/components/_overlay.scss b/src/styles/components/_overlay.scss index f7bb80b..9c9a293 100644 --- a/src/styles/components/_overlay.scss +++ b/src/styles/components/_overlay.scss @@ -1,12 +1,12 @@ #overlay { display: none; position: fixed; - z-index: 10; + z-index: 20; top: 0; left: 0; width: 100%; height: 100%; - transition: var(--transition-fast); + transition: opacity 200ms ease-in-out; background: var(--color-dark-80); user-select: none; will-change: opacity; diff --git a/src/styles/elements/_code.scss b/src/styles/elements/_code.scss index a5437c4..139ce9e 100644 --- a/src/styles/elements/_code.scss +++ b/src/styles/elements/_code.scss @@ -63,7 +63,6 @@ p code { border-right: 0; color: var(--color-07); text-align: center; - pointer-events: none; user-select: none; .highlight-line::before { diff --git a/src/styles/layouts/_header.scss b/src/styles/layouts/_header.scss index 07859f3..7c633d5 100644 --- a/src/styles/layouts/_header.scss +++ b/src/styles/layouts/_header.scss @@ -45,12 +45,6 @@ grid-area: theme; margin-left: var(--spacing-xs); } - - > #theme-switch { - @include breakpoint-down(sm) { - display: none; - } - } } .header-float #header {