✨ 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" />
|
/// <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">
|
<script lang="ts">
|
||||||
import { browser } from '$app/env'
|
import { browser } from '$app/env'
|
||||||
import { page } from '$app/stores'
|
import { page } from '$app/stores'
|
||||||
import dayjs from 'dayjs'
|
|
||||||
import advancedFormat from 'dayjs/plugin/advancedFormat.js'
|
|
||||||
import { getAssetUrlKey } from '$utils/helpers'
|
import { getAssetUrlKey } from '$utils/helpers'
|
||||||
import { throttle } from '$utils/functions'
|
import { throttle } from '$utils/functions'
|
||||||
|
import { swipe } from '$utils/interactions/swipe'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import advancedFormat from 'dayjs/plugin/advancedFormat.js'
|
||||||
// Components
|
// Components
|
||||||
import Metas from '$components/Metas.svelte'
|
import Metas from '$components/Metas.svelte'
|
||||||
import Image from '$components/atoms/Image.svelte'
|
import Image from '$components/atoms/Image.svelte'
|
||||||
@@ -23,6 +24,7 @@
|
|||||||
|
|
||||||
enum directions { PREV, NEXT }
|
enum directions { PREV, NEXT }
|
||||||
|
|
||||||
|
let innerWidth: number
|
||||||
let globalOffset = offset
|
let globalOffset = offset
|
||||||
let isLoading = false
|
let isLoading = false
|
||||||
let hasNext = offset + limit < countPhotos
|
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
|
* Load photos
|
||||||
@@ -152,7 +172,10 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window on:keydown={handleKeydown} />
|
<svelte:window
|
||||||
|
bind:innerWidth
|
||||||
|
on:keydown={handleKeydown}
|
||||||
|
/>
|
||||||
|
|
||||||
{#if currentPhoto}
|
{#if currentPhoto}
|
||||||
<Metas
|
<Metas
|
||||||
@@ -166,7 +189,7 @@
|
|||||||
<main class="viewer-photo">
|
<main class="viewer-photo">
|
||||||
<div class="container grid">
|
<div class="container grid">
|
||||||
<div class="viewer-photo__carousel">
|
<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)}
|
{#each visiblePhotos as photo, index (photo.id)}
|
||||||
<Image
|
<Image
|
||||||
class="photo photo--{currentIndex === 0 ? index + 1 : index}"
|
class="photo photo--{currentIndex === 0 ? index + 1 : index}"
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ body {
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overscroll-behavior-y: none;
|
overscroll-behavior: none;
|
||||||
}
|
}
|
||||||
*, *:before, *:after {
|
*, *:before, *:after {
|
||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
height: 100vh;
|
height: 100vh;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -37,6 +38,7 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
margin: auto auto 0;
|
margin: auto auto 0;
|
||||||
padding-top: 66.66%;
|
padding-top: 66.66%;
|
||||||
|
touch-action: none;
|
||||||
|
|
||||||
.photo {
|
.photo {
|
||||||
--opacity: 1;
|
--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