Skip to content

Instantly share code, notes, and snippets.

@tomsoderlund
Created February 11, 2021 12:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tomsoderlund/46f8635da8c07477e90024fbef282f16 to your computer and use it in GitHub Desktop.
Save tomsoderlund/46f8635da8c07477e90024fbef282f16 to your computer and use it in GitHub Desktop.
Scrolling 3D navigation
import React from 'react'
import ScrollingNavigation, { useScrollingNavigation } from 'components/page/ScrollingNavigation'
function StartPage ({ title }) {
return (
<ScrollingNavigation
startX={0}
maxX={2000}
>
<StartPageContents />
</ScrollingNavigation>
)
}
export default StartPage
export const getStaticProps = () => ({
props: {
includeMain: false // used in _app.js
}
})
const Arrow = () => (
<svg width='40' height='40' viewBox='0 0 120 121' fill='none' xmlns='http://www.w3.org/2000/svg'>
<path fill='white' d='M118.663 52.5122H105.087C104.316 52.5122 103.579 52.8474 103.059 53.4341L66.3692 95.7221V1.34088C66.3692 0.603396 65.7658 0 65.0283 0H54.9717C54.2342 0 53.6308 0.603396 53.6308 1.34088V95.7221L16.941 53.4341C16.4382 52.8474 15.7007 52.5122 14.9129 52.5122H1.3365C0.196753 52.5122 -0.423404 53.8699 0.330841 54.7247L55.9606 118.835C56.4635 119.416 57.0851 119.881 57.7835 120.2C58.4819 120.519 59.2406 120.684 60.0084 120.684C60.7761 120.684 61.5349 120.519 62.2332 120.2C62.9316 119.881 63.5533 119.416 64.0562 118.835L119.669 54.7247C120.423 53.8531 119.803 52.5122 118.663 52.5122Z' />
</svg>
)
const StartPageContents = () => {
const { get3dElementStyles } = useScrollingNavigation()
return (
<>
<div
className='box turquoise'
style={get3dElementStyles(0, 500)}
>
<h1>I am Tom</h1>
<button onClick={e => window.alert('Clicked!')}>Click here</button>
<Arrow />
</div>
<div
className='box red'
style={get3dElementStyles(0, 1500)}
>
<h1>I like to tinker</h1>
</div>
<div
className='box purple'
style={get3dElementStyles(0, 2500)}
>
<h1>And build stuff</h1>
</div>
<div
className='box purple'
style={get3dElementStyles(1500, 2500)}
>
<h1>Here are some projects:</h1>
</div>
<style jsx>{`
:global(body) {
background-color: #222 !important;
font-size: 24px !important;
}
.box {
position: absolute;
width: 100%;
height: 100%;
z-index: 100;
transform-style: preserve-3d;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: white;
}
.turquoise {
background-color: darkturquoise;
}
.purple {
background-color: rebeccapurple;
}
.red {
background-color: salmon;
}
.gray-light {
background-color: whitesmoke;
}
.gray-dark {
background-color: #555555;
}
button {
pointer-events: all;
}
`}
</style>
</>
)
}
import React, { createContext, useContext, useState, useEffect, useRef } from 'react'
const ScrollingNavigation = ({ children, maxX, maxY = 2000, startX = 0, startY = 0, scrollSpeed = 2.0 }) => {
const scrollingRef = useRef()
return (
<ScrollContextProvider
startX={startX}
startY={startY}
scrollSpeed={scrollSpeed}
scrollingRef={scrollingRef}
>
<ScrollingInside
children={children}
scrollingRef={scrollingRef}
maxX={maxX}
maxY={maxY}
/>
</ScrollContextProvider>
)
}
export default ScrollingNavigation
const ScrollingInside = ({ children, scrollingRef, maxX, maxY }) => {
const { handleScroll } = useScrollingNavigation()
return (
<section
className='scrollingnavigation-container'
ref={scrollingRef}
onScroll={handleScroll}
>
<div className='scrolling-block' />
<div className='fixed-container'>
{children}
</div>
<style jsx>{`
.scrollingnavigation-container {
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
overflow: auto;
}
.scrolling-block {
z-index: 100;
width: ${maxX ? `${maxX}px` : 'auto'};
height: ${maxY ? `${maxY}px` : 'auto'};
}
.fixed-container {
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
pointer-events: none;
perspective: 500px;
/* border: 4px solid red; */
}
`}
</style>
</section>
)
}
const OBJECT_FADEOUT_START_Z = 0
const OBJECT_FADEOUT_Z = 450
const ScrollContext = createContext()
const ScrollContextProvider = ({ children, scrollingRef, startX, startY, scrollSpeed }) => {
const [position, setPosition] = useState({ x: 0, y: 0 })
useEffect(() => {
scrollingRef.current.scrollLeft = startX
scrollingRef.current.scrollTop = startY
}, [startX, startY])
const handleScroll = ({ target }) => {
setPosition({
x: -target.scrollLeft * scrollSpeed,
y: target.scrollTop * scrollSpeed
})
}
// const handleWheel = (e) => setAcceleration(acceleration + (e.deltaY > 0 ? SCROLL_FACTOR : (e.deltaY < 0 ? -SCROLL_FACTOR : 0)))
const get3dElementStyles = (elementX = 0, elementY = 0) => {
const elementPosition = {
x: position.x + elementX,
y: position.y - elementY
}
return {
transform: `translateZ(${elementPosition.y}px) translateX(${elementPosition.x}px)`,
opacity: (elementPosition.y > OBJECT_FADEOUT_START_Z) ? ((OBJECT_FADEOUT_Z - elementPosition.y) / OBJECT_FADEOUT_Z) : 1,
zIndex: 5000 - elementY
}
}
// Make the context object
const articlesContext = { position, handleScroll, get3dElementStyles }
// Pass the value in Provider and return
return <ScrollContext.Provider value={articlesContext}>{children}</ScrollContext.Provider>
}
// const { Consumer: ScrollContextConsumer } = ScrollContext
export const useScrollingNavigation = () => useContext(ScrollContext)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment