Add mobile improvements
This commit is contained in:
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "wow-best-places",
|
"name": "wow-best-places",
|
||||||
"description": "This app is supposed to make you feel nostalgic",
|
"description": "This app is supposed to make you feel nostalgic",
|
||||||
"version": "1.2.0",
|
"version": "1.3.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "obergodmar",
|
"name": "obergodmar",
|
||||||
"email": "obergodmar@gmail.com",
|
"email": "obergodmar@gmail.com",
|
||||||
|
|||||||
+13
-8
@@ -69,9 +69,13 @@ export default function App() {
|
|||||||
})
|
})
|
||||||
}, [isLoading])
|
}, [isLoading])
|
||||||
|
|
||||||
const handleLeftPreviewClick = (value: number) => delayedChange(setActivePlace, value)
|
const handleLeftPreviewClick = useCallback((value: number) =>
|
||||||
|
delayedChange(setActivePlace, value),
|
||||||
|
[delayedChange])
|
||||||
|
|
||||||
const handleBottomPreviewClick = (value: number) => delayedChange(setActiveView, value)
|
const handleBottomPreviewClick = useCallback((value: number) =>
|
||||||
|
delayedChange(setActiveView, value),
|
||||||
|
[delayedChange])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (app && app.current) {
|
if (app && app.current) {
|
||||||
@@ -89,11 +93,12 @@ export default function App() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
currentPlaying.setVolume(musicVolume)
|
currentPlaying.setVolume(musicVolume)
|
||||||
|
currentPlaying.playMusic()
|
||||||
}, [currentPlaying, musicVolume])
|
}, [currentPlaying, musicVolume])
|
||||||
|
|
||||||
const appClick = () => currentPlaying && currentPlaying.playMusic()
|
const appClick = useCallback(() => currentPlaying && currentPlaying.playMusic(), [currentPlaying])
|
||||||
|
|
||||||
const openCloseSettings = () => {
|
const openCloseSettings = useCallback(() => {
|
||||||
setSettingsShown(!isSettingsShown)
|
setSettingsShown(!isSettingsShown)
|
||||||
if (app && app.current) {
|
if (app && app.current) {
|
||||||
app.current.focus()
|
app.current.focus()
|
||||||
@@ -106,9 +111,9 @@ export default function App() {
|
|||||||
} else {
|
} else {
|
||||||
settingsOpenSound.playSound()
|
settingsOpenSound.playSound()
|
||||||
}
|
}
|
||||||
}
|
}, [app, isSettingsShown, uiSound, settingsCloseSound, settingsOpenSound])
|
||||||
|
|
||||||
const handleOpenSettings = (e: KeyboardEvent) => {
|
const handleOpenSettings = useCallback((e: KeyboardEvent) => {
|
||||||
switch (e.keyCode) {
|
switch (e.keyCode) {
|
||||||
case 27:
|
case 27:
|
||||||
if (isLeftPanelShown || isBottomPanelShown) {
|
if (isLeftPanelShown || isBottomPanelShown) {
|
||||||
@@ -129,7 +134,7 @@ export default function App() {
|
|||||||
currentPlaying.playMusic()
|
currentPlaying.playMusic()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}, [isLeftPanelShown, isBottomPanelShown, isPlaying, currentPlaying, openCloseSettings])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -143,7 +148,7 @@ export default function App() {
|
|||||||
<MainMenuComponent>
|
<MainMenuComponent>
|
||||||
<div className='author'>
|
<div className='author'>
|
||||||
<a href="https://github.com/obergodmar">obergodmar</a>
|
<a href="https://github.com/obergodmar">obergodmar</a>
|
||||||
<span>v1.2.0</span>
|
<span>v1.3.0</span>
|
||||||
</div>
|
</div>
|
||||||
<MenuItemComponent
|
<MenuItemComponent
|
||||||
isActive={isSettingsShown}
|
isActive={isSettingsShown}
|
||||||
|
|||||||
+3
-1
@@ -38,13 +38,15 @@ body {
|
|||||||
|
|
||||||
.author {
|
.author {
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
width: 130px;
|
width: 0;
|
||||||
|
opacity: 0;
|
||||||
font-family: $font;
|
font-family: $font;
|
||||||
text-shadow: $fontShadow;
|
text-shadow: $fontShadow;
|
||||||
color: $fontColor;
|
color: $fontColor;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
transition: width $transitionDuration $transitionType, opacity $transitionDuration $transitionType;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { KeyboardEvent } from 'react'
|
import { KeyboardEvent, useCallback } from 'react'
|
||||||
|
|
||||||
import './checkbox-component.scss'
|
import './checkbox-component.scss'
|
||||||
|
|
||||||
@@ -11,12 +11,13 @@ interface Props {
|
|||||||
|
|
||||||
export const CheckBoxComponent = ({handleClick, optionName, value}: Props) => {
|
export const CheckBoxComponent = ({handleClick, optionName, value}: Props) => {
|
||||||
|
|
||||||
const handleKeyDown = (e: KeyboardEvent, option: any) => {
|
const handleKeyDown = useCallback((e: KeyboardEvent, option: any) => {
|
||||||
if (e.keyCode !== 13 && e.keyCode !== 32) {
|
if (e.keyCode !== 13 && e.keyCode !== 32) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
handleClick(option)
|
handleClick(option)
|
||||||
}
|
}, [handleClick])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
|
|||||||
@@ -18,4 +18,11 @@ $menuWidth: 290px;
|
|||||||
border-image-repeat: round round;
|
border-image-repeat: round round;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
padding: 2px 3px;
|
padding: 2px 3px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.author {
|
||||||
|
width: 130px;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ $menuItemWidth: 32px;
|
|||||||
$menuItemHeight: 41px;
|
$menuItemHeight: 41px;
|
||||||
|
|
||||||
.menu-item {
|
.menu-item {
|
||||||
|
z-index: 4;
|
||||||
width: $menuItemWidth - 4px;
|
width: $menuItemWidth - 4px;
|
||||||
height: $menuItemHeight - 4px;
|
height: $menuItemHeight - 4px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { KeyboardEvent } from 'react'
|
import { KeyboardEvent, useCallback } from 'react'
|
||||||
|
|
||||||
import './menu-item-component.scss'
|
import './menu-item-component.scss'
|
||||||
|
|
||||||
@@ -10,12 +10,12 @@ interface Props {
|
|||||||
|
|
||||||
export const MenuItemComponent = ({isActive, handleClick}: Props) => {
|
export const MenuItemComponent = ({isActive, handleClick}: Props) => {
|
||||||
|
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
const handleKeyDown = useCallback((e: KeyboardEvent) => {
|
||||||
if (e.keyCode !== 13 && e.keyCode !== 32) {
|
if (e.keyCode !== 13 && e.keyCode !== 32) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
handleClick()
|
handleClick()
|
||||||
}
|
}, [handleClick])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
WheelEvent
|
WheelEvent
|
||||||
} from 'react'
|
} from 'react'
|
||||||
|
|
||||||
import { ANIMATION_DURATION, debounce, PREVIEW_HEIGHT, PREVIEW_WIDTH } from '../../utils'
|
import { ANIMATION_DURATION, debounce, PREVIEW_HEIGHT, PREVIEW_WIDTH, SPACE } from '../../utils'
|
||||||
import { useSettings } from '../../hooks'
|
import { useSettings } from '../../hooks'
|
||||||
|
|
||||||
import './panel-component.scss'
|
import './panel-component.scss'
|
||||||
@@ -37,7 +37,7 @@ export const PanelComponent = ({
|
|||||||
}: Props) => {
|
}: Props) => {
|
||||||
const {settings: {language, uiSound}} = useSettings()
|
const {settings: {language, uiSound}} = useSettings()
|
||||||
const [isDrag, setDrag] = useState(false)
|
const [isDrag, setDrag] = useState(false)
|
||||||
const [trackMouse, setTrackMouse] = useState(0)
|
const [trackPosition, setTrackPosition] = useState(0)
|
||||||
const [position, setPosition] = useState(0)
|
const [position, setPosition] = useState(0)
|
||||||
const [lastPosition, setLastPosition] = useState(0)
|
const [lastPosition, setLastPosition] = useState(0)
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ export const PanelComponent = ({
|
|||||||
panel.current.style.transition = `transform 0.5s`
|
panel.current.style.transition = `transform 0.5s`
|
||||||
}
|
}
|
||||||
panel.current.style.transform = `unset`
|
panel.current.style.transform = `unset`
|
||||||
setTrackMouse(0)
|
setTrackPosition(0)
|
||||||
setPosition(0)
|
setPosition(0)
|
||||||
setLastPosition(0)
|
setLastPosition(0)
|
||||||
}, [panel])
|
}, [panel])
|
||||||
@@ -123,9 +123,9 @@ export const PanelComponent = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const limiter = (value: number, overflow: number, isWheel: boolean = false) => {
|
const limiter = (value: number, overflow: number, isWheel: boolean = false) => {
|
||||||
let diff = (!isWheel ? trackMouse : 0) - value + lastPosition
|
let diff = (!isWheel ? trackPosition : 0) - value + lastPosition
|
||||||
if (Math.abs(diff) > overflow + 40) {
|
if (Math.abs(diff) > overflow + SPACE) {
|
||||||
diff = overflow + 40
|
diff = overflow + SPACE
|
||||||
} else if (diff < 0) {
|
} else if (diff < 0) {
|
||||||
diff = 0
|
diff = 0
|
||||||
}
|
}
|
||||||
@@ -142,14 +142,14 @@ export const PanelComponent = ({
|
|||||||
const handleMouseDown = (e: MouseEvent) => {
|
const handleMouseDown = (e: MouseEvent) => {
|
||||||
const {clientX, clientY} = e
|
const {clientX, clientY} = e
|
||||||
e.nativeEvent.stopImmediatePropagation()
|
e.nativeEvent.stopImmediatePropagation()
|
||||||
setTrackMouse(isBottom ? clientX : clientY)
|
setTrackPosition(isBottom ? clientX : clientY)
|
||||||
setDrag(true)
|
setDrag(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleTouchstart = (e: TouchEvent) => {
|
const handleTouchstart = (e: TouchEvent) => {
|
||||||
const {touches} = e
|
const {touches} = e
|
||||||
e.nativeEvent.stopImmediatePropagation()
|
e.nativeEvent.stopImmediatePropagation()
|
||||||
setTrackMouse(isBottom ? touches[0].clientX : touches[0].clientY)
|
setTrackPosition(isBottom ? touches[0].clientX : touches[0].clientY)
|
||||||
setDrag(true)
|
setDrag(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@ export const PanelComponent = ({
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const value = deltaY > 0 ? 80 : -80
|
const value = deltaY > 0 ? -80 : 80
|
||||||
const diff = limiter(value, overflow, true)
|
const diff = limiter(value, overflow, true)
|
||||||
setPosition(diff)
|
setPosition(diff)
|
||||||
changePosition()
|
changePosition()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useEffect, useMemo, useState } from 'react'
|
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
|
|
||||||
import { useSettings } from '../../hooks'
|
import { useSettings } from '../../hooks'
|
||||||
import { Plug } from '../../assets'
|
import { Plug } from '../../assets'
|
||||||
@@ -33,10 +33,10 @@ export const PreviewComponent = ({name = '', src, value, handleChange, isLoading
|
|||||||
}
|
}
|
||||||
}, [image])
|
}, [image])
|
||||||
|
|
||||||
const handleClick = (e: React.MouseEvent) => {
|
const handleClick = useCallback((e: React.MouseEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
handleChange(value)
|
handleChange(value)
|
||||||
}
|
}, [handleChange, value])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ export const RangeComponent = ({handleChange, defaultValue}: Props) => {
|
|||||||
|
|
||||||
const stick = useRef<HTMLDivElement>(null)
|
const stick = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
|
const handleFocus = useCallback(() => setPressed(true), [])
|
||||||
|
const handleFree = useCallback(() => setPressed(false), [])
|
||||||
|
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
if (!stick || !stick.current || !stick.current.parentNode) {
|
if (!stick || !stick.current || !stick.current.parentNode) {
|
||||||
return
|
return
|
||||||
@@ -23,43 +26,57 @@ export const RangeComponent = ({handleChange, defaultValue}: Props) => {
|
|||||||
const {width} = (stick.current.parentNode as HTMLDivElement).getBoundingClientRect()
|
const {width} = (stick.current.parentNode as HTMLDivElement).getBoundingClientRect()
|
||||||
switch (e.keyCode) {
|
switch (e.keyCode) {
|
||||||
case 37:
|
case 37:
|
||||||
if (position - 5 < 0) {
|
const minusValue = position - 5
|
||||||
return
|
const minusDiff = limiter(minusValue, width)
|
||||||
}
|
|
||||||
setPosition(position - 5)
|
setPosition(minusDiff)
|
||||||
handleChange(position / MAX)
|
handleChange(minusDiff / MAX)
|
||||||
break
|
break
|
||||||
case 39:
|
case 39:
|
||||||
if (position + 5 > width - 35) {
|
const plusValue = position + 5
|
||||||
return
|
const plusDiff = limiter(plusValue, width)
|
||||||
}
|
|
||||||
setPosition(position + 5)
|
setPosition(plusDiff)
|
||||||
handleChange(position / MAX)
|
handleChange(plusDiff / MAX)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleMouseDown = useCallback(() => {
|
const handlePoint = useCallback((e: React.MouseEvent | MouseEvent) => {
|
||||||
setPressed(true)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const handleMouseUp = useCallback(() => {
|
|
||||||
setPressed(false)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const handleMouseMove = useCallback((e: MouseEvent) => {
|
|
||||||
if (!isPressed) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!stick || !stick.current || !stick.current.parentNode) {
|
if (!stick || !stick.current || !stick.current.parentNode) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const {width, left} = (stick.current.parentNode as HTMLDivElement).getBoundingClientRect()
|
const {width, left} = (stick.current.parentNode as HTMLDivElement).getBoundingClientRect()
|
||||||
const {clientX} = e
|
const {clientX} = e
|
||||||
const diff = clientX - left - 20
|
const value = clientX - left - 20
|
||||||
if (diff > width - 35 || diff < 0) {
|
const diff = limiter(value, width)
|
||||||
|
|
||||||
|
setPosition(diff)
|
||||||
|
handleChange(diff / MAX)
|
||||||
|
}, [stick, handleChange])
|
||||||
|
|
||||||
|
const handleMouseMove = useCallback((e: MouseEvent) => {
|
||||||
|
if (!isPressed) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
handlePoint(e)
|
||||||
|
}, [isPressed, handlePoint])
|
||||||
|
|
||||||
|
const handleTouchMove = useCallback((e: TouchEvent) => {
|
||||||
|
if (!isPressed) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const {touches} = e
|
||||||
|
const {clientX} = touches[0]
|
||||||
|
|
||||||
|
if (!stick || !stick.current || !stick.current.parentNode) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const {width, left} = (stick.current.parentNode as HTMLDivElement).getBoundingClientRect()
|
||||||
|
|
||||||
|
const value = clientX - left - 20
|
||||||
|
const diff = limiter(value, width)
|
||||||
|
|
||||||
setPosition(diff)
|
setPosition(diff)
|
||||||
handleChange(diff / MAX)
|
handleChange(diff / MAX)
|
||||||
}, [isPressed, stick, handleChange])
|
}, [isPressed, stick, handleChange])
|
||||||
@@ -73,27 +90,42 @@ export const RangeComponent = ({handleChange, defaultValue}: Props) => {
|
|||||||
range.focus()
|
range.focus()
|
||||||
const {deltaY} = e
|
const {deltaY} = e
|
||||||
const value = position + (deltaY > 0 ? -5 : 5)
|
const value = position + (deltaY > 0 ? -5 : 5)
|
||||||
if (value > width - 35 || value < 0) {
|
const diff = limiter(value, width)
|
||||||
return
|
|
||||||
|
setPosition(diff)
|
||||||
|
handleChange(diff / MAX)
|
||||||
}
|
}
|
||||||
setPosition(value)
|
|
||||||
handleChange(value / MAX)
|
const limiter = (value: number, width: number) => {
|
||||||
|
let diff = value
|
||||||
|
if (diff > width - 35) {
|
||||||
|
diff = MAX
|
||||||
|
} else if (diff < 0) {
|
||||||
|
diff = 0
|
||||||
|
}
|
||||||
|
return diff
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.addEventListener('mousemove', handleMouseMove)
|
window.addEventListener('mousemove', handleMouseMove)
|
||||||
window.addEventListener('mouseup', handleMouseUp)
|
window.addEventListener('mouseup', handleFree)
|
||||||
|
window.addEventListener('touchmove', handleTouchMove)
|
||||||
|
window.addEventListener('touchend', handleFree)
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('mousemove', handleMouseMove)
|
window.removeEventListener('mousemove', handleMouseMove)
|
||||||
window.removeEventListener('mouseup', handleMouseUp)
|
window.removeEventListener('mouseup', handleFree)
|
||||||
|
window.removeEventListener('touchmove', handleTouchMove)
|
||||||
|
window.removeEventListener('touchend', handleFree)
|
||||||
}
|
}
|
||||||
}, [handleMouseMove, handleMouseUp])
|
}, [handleMouseMove, handleTouchMove, handleFree])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
onMouseDown={handleMouseDown}
|
onMouseDown={handleFocus}
|
||||||
|
onClick={handlePoint}
|
||||||
|
onTouchStart={handleFocus}
|
||||||
onWheel={handleScroll}
|
onWheel={handleScroll}
|
||||||
className='range'
|
className='range'
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { FocusEvent, KeyboardEvent, useRef, useState } from 'react'
|
import { FocusEvent, KeyboardEvent, useCallback, useRef, useState } from 'react'
|
||||||
|
|
||||||
import './select-component.scss'
|
import './select-component.scss'
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ export const SelectComponent = ({children, options, current, handleChange}: Prop
|
|||||||
const [isSelectShown, setSelectShown] = useState(false)
|
const [isSelectShown, setSelectShown] = useState(false)
|
||||||
const dropDownRef = useRef<HTMLDivElement>(null)
|
const dropDownRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
const handleSelectClick = () => setSelectShown(!isSelectShown)
|
const handleSelectClick = useCallback(() => setSelectShown(!isSelectShown), [isSelectShown])
|
||||||
|
|
||||||
const handleBlur = (e: FocusEvent) => {
|
const handleBlur = (e: FocusEvent) => {
|
||||||
if (!dropDownRef || !dropDownRef.current) {
|
if (!dropDownRef || !dropDownRef.current) {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
min-width: 100vw;
|
min-width: 100vw;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background-position: center;
|
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
animation: pulse 10s infinite;
|
animation: pulse 10s infinite;
|
||||||
animation-direction: alternate;
|
animation-direction: alternate;
|
||||||
@@ -18,6 +17,7 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
min-width: inherit;
|
min-width: inherit;
|
||||||
min-height: inherit;
|
min-height: inherit;
|
||||||
|
background-size: cover;
|
||||||
background: $backgroundTexture;
|
background: $backgroundTexture;
|
||||||
transition: opacity $transitionDuration $transitionType;
|
transition: opacity $transitionDuration $transitionType;
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useEffect, useState } from 'react'
|
import { FocusEvent, MouseEvent, TouchEvent, useCallback, useEffect, useState } from 'react'
|
||||||
|
|
||||||
import { Background } from '../../assets'
|
import { Background } from '../../assets'
|
||||||
import { ANIMATION_DURATION } from '../../utils'
|
import { ANIMATION_DURATION, DEFAULT_HEIGHT, DEFAULT_WIDTH } from '../../utils'
|
||||||
|
|
||||||
import './view-component.scss'
|
import './view-component.scss'
|
||||||
|
|
||||||
@@ -10,11 +10,42 @@ interface Props {
|
|||||||
src: string
|
src: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Position {
|
||||||
|
x: number
|
||||||
|
y: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialPosition = {
|
||||||
|
x: 0,
|
||||||
|
y: 0
|
||||||
|
}
|
||||||
|
|
||||||
export const ViewComponent = ({src}: Props) => {
|
export const ViewComponent = ({src}: Props) => {
|
||||||
const [imageSrc, setImageSrc] = useState(Background)
|
const [imageSrc, setImageSrc] = useState(Background)
|
||||||
const [isLoaded, setLoaded] = useState(false)
|
const [isLoaded, setLoaded] = useState(false)
|
||||||
|
const [isDrag, setDrag] = useState(false)
|
||||||
|
const [trackPosition, setTrackPosition] = useState(initialPosition)
|
||||||
|
const [position, setPosition] = useState(initialPosition)
|
||||||
|
const [lastPosition, setLastPosition] = useState(initialPosition)
|
||||||
|
|
||||||
|
const handleResize = useCallback(() => {
|
||||||
|
const {innerWidth, innerHeight} = window
|
||||||
|
|
||||||
|
let width = (innerWidth - DEFAULT_WIDTH) / 2
|
||||||
|
let height = (innerHeight - DEFAULT_HEIGHT) / 2
|
||||||
|
|
||||||
|
if (innerWidth >= DEFAULT_WIDTH) {
|
||||||
|
width = 0
|
||||||
|
}
|
||||||
|
if (innerHeight >= DEFAULT_HEIGHT) {
|
||||||
|
height = 0
|
||||||
|
}
|
||||||
|
setPosition({x: width, y: height})
|
||||||
|
setLastPosition({x: width, y: height})
|
||||||
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
handleResize()
|
||||||
setLoaded(false)
|
setLoaded(false)
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
const image = new Image()
|
const image = new Image()
|
||||||
@@ -27,14 +58,97 @@ export const ViewComponent = ({src}: Props) => {
|
|||||||
return () => {
|
return () => {
|
||||||
clearTimeout(timer)
|
clearTimeout(timer)
|
||||||
}
|
}
|
||||||
}, [src])
|
}, [src, handleResize])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener('resize', handleResize)
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('resize', handleResize)
|
||||||
|
}
|
||||||
|
}, [handleResize])
|
||||||
|
|
||||||
|
const handleTouchMove = (e: TouchEvent) => {
|
||||||
|
const {touches} = e
|
||||||
|
const {innerWidth, innerHeight} = window
|
||||||
|
const width = DEFAULT_WIDTH - innerWidth
|
||||||
|
const height = DEFAULT_HEIGHT - innerHeight
|
||||||
|
const {clientX: x, clientY: y} = touches[0]
|
||||||
|
|
||||||
|
const diff = limiter({x, y}, width, height)
|
||||||
|
|
||||||
|
setPosition(diff)
|
||||||
|
}
|
||||||
|
|
||||||
|
const limiter = (value: Position, width: number, height: number) => {
|
||||||
|
const {x: xValue, y: yValue} = value
|
||||||
|
|
||||||
|
let x = trackPosition.x - xValue + lastPosition.x
|
||||||
|
let y = trackPosition.y - yValue + lastPosition.y
|
||||||
|
|
||||||
|
if (x > 0) {
|
||||||
|
x = 0
|
||||||
|
} else if (x < -width) {
|
||||||
|
x = -width
|
||||||
|
}
|
||||||
|
|
||||||
|
if (y > 0) {
|
||||||
|
y = 0
|
||||||
|
} else if (y < -height) {
|
||||||
|
y = -height
|
||||||
|
}
|
||||||
|
|
||||||
|
return ({x, y})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleTouchstart = (e: TouchEvent) => {
|
||||||
|
const {touches} = e
|
||||||
|
e.nativeEvent.stopImmediatePropagation()
|
||||||
|
const {clientX: x, clientY: y} = touches[0]
|
||||||
|
setTrackPosition({x, y})
|
||||||
|
setDrag(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMouseDown = (e: MouseEvent) => {
|
||||||
|
const {clientX, clientY} = e
|
||||||
|
e.nativeEvent.stopImmediatePropagation()
|
||||||
|
setTrackPosition({x: clientX, y: clientY})
|
||||||
|
setDrag(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleFree = (e: MouseEvent | FocusEvent | TouchEvent) => {
|
||||||
|
e.nativeEvent.stopImmediatePropagation()
|
||||||
|
setDrag(false)
|
||||||
|
setLastPosition(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDragScroll = (e: MouseEvent) => {
|
||||||
|
if (!isDrag) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const {innerWidth, innerHeight} = window
|
||||||
|
const width = DEFAULT_WIDTH - innerWidth
|
||||||
|
const height = DEFAULT_HEIGHT - innerHeight
|
||||||
|
|
||||||
|
const {clientX: x, clientY: y} = e
|
||||||
|
const diff = limiter({x, y}, width, height)
|
||||||
|
setPosition(diff)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className='view'
|
className='view'
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: `url(${imageSrc})`
|
backgroundImage: `url(${imageSrc})`,
|
||||||
|
backgroundPosition: `${position.x}px ${position.y}px`
|
||||||
}}
|
}}
|
||||||
|
onMouseDown={handleMouseDown}
|
||||||
|
onMouseMove={handleDragScroll}
|
||||||
|
onMouseUp={handleFree}
|
||||||
|
onTouchMove={handleTouchMove}
|
||||||
|
onTouchStart={handleTouchstart}
|
||||||
|
onTouchEnd={handleFree}
|
||||||
|
onMouseLeave={handleFree}
|
||||||
|
onBlur={handleFree}
|
||||||
>
|
>
|
||||||
<div className={`view-background ${isLoaded ? 'view-background--loaded' : ''}`} />
|
<div className={`view-background ${isLoaded ? 'view-background--loaded' : ''}`} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,3 +4,6 @@ export const PREVIEW_WIDTH = 320
|
|||||||
export const PREVIEW_HEIGHT = 180
|
export const PREVIEW_HEIGHT = 180
|
||||||
export const UI_SOUND_VOLUME = 0.2
|
export const UI_SOUND_VOLUME = 0.2
|
||||||
export const UI_MUSIC_VOLUME = 1
|
export const UI_MUSIC_VOLUME = 1
|
||||||
|
export const SPACE = 200
|
||||||
|
export const DEFAULT_WIDTH = 1920
|
||||||
|
export const DEFAULT_HEIGHT = 1080
|
||||||
|
|||||||
+4
-1
@@ -7,7 +7,10 @@ export {
|
|||||||
UI_MUSIC_VOLUME,
|
UI_MUSIC_VOLUME,
|
||||||
UI_SOUND_VOLUME,
|
UI_SOUND_VOLUME,
|
||||||
LOADING_DURATION,
|
LOADING_DURATION,
|
||||||
ANIMATION_DURATION
|
ANIMATION_DURATION,
|
||||||
|
DEFAULT_HEIGHT,
|
||||||
|
DEFAULT_WIDTH,
|
||||||
|
SPACE
|
||||||
} from './constants'
|
} from './constants'
|
||||||
|
|
||||||
export const delay = () => new Promise(resolve => setTimeout(resolve, LOADING_DURATION))
|
export const delay = () => new Promise(resolve => setTimeout(resolve, LOADING_DURATION))
|
||||||
|
|||||||
Reference in New Issue
Block a user