Skip to content

Commit

Permalink
feat: improved lightbox
Browse files Browse the repository at this point in the history
fixed lightbox no longer opening
better closing handling
improved overlay and added events
revoked pointer-events restriction to important parts

Closes: #15
  • Loading branch information
Marvin Heilemann committed Mar 3, 2020
1 parent ea81072 commit acc1825
Show file tree
Hide file tree
Showing 10 changed files with 108 additions and 107 deletions.
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
6 changes: 0 additions & 6 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

159 changes: 82 additions & 77 deletions src/components/Lightbox.jsx
Original file line number Diff line number Diff line change
@@ -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()
Expand All @@ -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
Expand All @@ -45,105 +39,116 @@ 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
}
}
}
if (scale * clientWidth > naturalWidth) {
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 (
<figure className="lightbox gatsby-resp-image-figure" style={globalStyle}>
<div
className=" gatsby-resp-image-wrapper"
style={wrapper.props.style}
onClick={this.onClick}
onKeyPress={this.onClick}
role="presentation"
ref={this.elementRef}
>
{wrapper.props.children}
</div>
<figcaption className="gatsby-resp-image-figcaption">
{figcaption.props.children}
</figcaption>
</figure>
)
}
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 (
<figure className="lightbox" style={globalStyle}>
<div
className="resp-image-link"
style={wrapper.style}
data-src={link.href}
onClick={onClick}
onKeyPress={onClick}
role="presentation"
rel="lightbox"
ref={elementRef}
>
<div className="resp-image-wrapper">{link.children}</div>
</div>
<figcaption className="resp-image-figcaption">{figcaption.children}</figcaption>
</figure>
)
}

export default Lightbox
6 changes: 4 additions & 2 deletions src/hooks/use-overlay.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
12 changes: 9 additions & 3 deletions src/provider/overlay.js
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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 (
<OverlayContext.Provider value={{ show, hide }}>
<OverlayContext.Provider value={{ show, hide, on }}>
<div
id="overlay"
ref={elementRef}
style={{ display: visible ? 'block' : 'none' }}
onClick={(event) => events.click(event)}
role="presentation"
></div>

{children}
Expand Down
1 change: 0 additions & 1 deletion src/styles/_reset.scss
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@ img,
video {
max-width: 100%;
height: auto;
pointer-events: none;
user-select: none;
object-fit: cover;
}
Expand Down
17 changes: 10 additions & 7 deletions src/styles/components/_lightbox.scss
Original file line number Diff line number Diff line change
@@ -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);
Expand Down
4 changes: 2 additions & 2 deletions src/styles/components/_overlay.scss
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
1 change: 0 additions & 1 deletion src/styles/elements/_code.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
6 changes: 0 additions & 6 deletions src/styles/layouts/_header.scss
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,6 @@
grid-area: theme;
margin-left: var(--spacing-xs);
}

> #theme-switch {
@include breakpoint-down(sm) {
display: none;
}
}
}

.header-float #header {
Expand Down

0 comments on commit acc1825

Please sign in to comment.