✨ Add swipe gesture on photo Viewer
This commit is contained in:
10
src/global.d.ts
vendored
10
src/global.d.ts
vendored
@@ -1 +1,11 @@
|
||||
/// <reference types="@sveltejs/kit" />
|
||||
|
||||
/**
|
||||
* Custom Events
|
||||
*/
|
||||
// Swipe
|
||||
declare namespace svelte.JSX {
|
||||
interface HTMLAttributes<T> {
|
||||
onswipe?: (event: CustomEvent<string> & { target: EventTarget & T }) => any
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { browser } from '$app/env'
|
||||
import { page } from '$app/stores'
|
||||
import dayjs from 'dayjs'
|
||||
import advancedFormat from 'dayjs/plugin/advancedFormat.js'
|
||||
import { getAssetUrlKey } from '$utils/helpers'
|
||||
import { throttle } from '$utils/functions'
|
||||
import { swipe } from '$utils/interactions/swipe'
|
||||
import dayjs from 'dayjs'
|
||||
import advancedFormat from 'dayjs/plugin/advancedFormat.js'
|
||||
// Components
|
||||
import Metas from '$components/Metas.svelte'
|
||||
import Image from '$components/atoms/Image.svelte'
|
||||
@@ -23,6 +24,7 @@
|
||||
|
||||
enum directions { PREV, NEXT }
|
||||
|
||||
let innerWidth: number
|
||||
let globalOffset = offset
|
||||
let isLoading = false
|
||||
let hasNext = offset + limit < countPhotos
|
||||
@@ -74,6 +76,24 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Enable swipe gestures
|
||||
const handleSwipe = ({ detail }: CustomEvent<string>) => {
|
||||
// Swipe up and down on mobile/small screens
|
||||
if (innerWidth < 992) {
|
||||
switch (detail) {
|
||||
case '-y': goToNext(); break;
|
||||
case 'y': goToPrevious(); break;
|
||||
}
|
||||
}
|
||||
// Swipe left and right on larger screens
|
||||
else {
|
||||
switch (detail) {
|
||||
case '-x': goToNext(); break;
|
||||
case 'x': goToPrevious(); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load photos
|
||||
@@ -152,7 +172,10 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={handleKeydown} />
|
||||
<svelte:window
|
||||
bind:innerWidth
|
||||
on:keydown={handleKeydown}
|
||||
/>
|
||||
|
||||
{#if currentPhoto}
|
||||
<Metas
|
||||
@@ -166,7 +189,7 @@
|
||||
<main class="viewer-photo">
|
||||
<div class="container grid">
|
||||
<div class="viewer-photo__carousel">
|
||||
<div class="viewer-photo__images">
|
||||
<div class="viewer-photo__images" use:swipe on:swipe={handleSwipe}>
|
||||
{#each visiblePhotos as photo, index (photo.id)}
|
||||
<Image
|
||||
class="photo photo--{currentIndex === 0 ? index + 1 : index}"
|
||||
|
||||
@@ -11,7 +11,7 @@ body {
|
||||
color: #fff;
|
||||
cursor: default;
|
||||
overflow-x: hidden;
|
||||
overscroll-behavior-y: none;
|
||||
overscroll-behavior: none;
|
||||
}
|
||||
*, *:before, *:after {
|
||||
text-rendering: optimizeLegibility;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
|
||||
.container {
|
||||
height: 100%;
|
||||
@@ -37,6 +38,7 @@
|
||||
width: 100%;
|
||||
margin: auto auto 0;
|
||||
padding-top: 66.66%;
|
||||
touch-action: none;
|
||||
|
||||
.photo {
|
||||
--opacity: 1;
|
||||
|
||||
91
src/utils/interactions/swipe.ts
Normal file
91
src/utils/interactions/swipe.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* Swipe action
|
||||
* @description Detects horizontal or vertical swipe on an element
|
||||
* @link Inspired by https://github.com/Rezi/svelte-gestures/blob/main/src/swipe.ts
|
||||
*/
|
||||
export const swipe = (
|
||||
node: HTMLElement,
|
||||
options: SwipeOptions = {
|
||||
travelX: 60,
|
||||
travelY: 30,
|
||||
timeframe: 1000,
|
||||
}
|
||||
) => {
|
||||
let startTime: number
|
||||
let startX: number
|
||||
let startY: number
|
||||
let endX: number
|
||||
let endY: number
|
||||
|
||||
if (!node) {
|
||||
throw new Error('No specified node')
|
||||
}
|
||||
|
||||
// Detect swipe direction from given values
|
||||
const detectDirection = (startX: number, startY: number, endX: number, endY: number) => {
|
||||
// If action has been made within the timeframe (after would return)
|
||||
if (Date.now() - startTime < options.timeframe) {
|
||||
const deltaX = endX - startX
|
||||
const deltaY = endY - startY
|
||||
const absX = Math.abs(deltaX)
|
||||
const absY = Math.abs(deltaY)
|
||||
|
||||
// Horizontal
|
||||
if (absX >= 2 * absY && absX > options.travelX) {
|
||||
return deltaX > 0 ? 'x' : '-x'
|
||||
}
|
||||
// Vertical
|
||||
else if (absY >= 2 * absX && absY > options.travelY) {
|
||||
return deltaY > 0 ? 'y' : '-y'
|
||||
}
|
||||
// Tap
|
||||
else return null
|
||||
}
|
||||
}
|
||||
|
||||
// Down event
|
||||
const onDown = ({ x, y }: PointerEvent) => {
|
||||
startX = x
|
||||
startY = y
|
||||
startTime = Date.now()
|
||||
}
|
||||
node.addEventListener('pointerdown', onDown, false)
|
||||
|
||||
|
||||
// Up event
|
||||
const onUp = ({ x, y }: PointerEvent) => {
|
||||
endX = x
|
||||
endY = y
|
||||
|
||||
// Get direction from values
|
||||
const direction = detectDirection(startX, startY, endX, endY)
|
||||
if (direction) {
|
||||
// Dispatch event if direction has been detected
|
||||
node.dispatchEvent(
|
||||
new CustomEvent('swipe', { detail: direction })
|
||||
)
|
||||
}
|
||||
}
|
||||
node.addEventListener('pointerup', onUp, false)
|
||||
|
||||
|
||||
return {
|
||||
/**
|
||||
* Destroy
|
||||
*/
|
||||
destroy () {
|
||||
node.removeEventListener('pointerdown', onDown)
|
||||
node.removeEventListener('pointerup', onUp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Types
|
||||
*/
|
||||
interface SwipeOptions {
|
||||
travelX?: number
|
||||
travelY?: number
|
||||
timeframe?: number
|
||||
}
|
||||
Reference in New Issue
Block a user