Swipe-to-pay component using useSwipe
implementing GrabPay swipe to pay button
Demo
Pay RM 1.00
This only works on mobile. For desktop implementation, useusePointerSwipe
How to use
Here is an example of how to use the component
<template>
<p>Pinjam tng-input for demo</p>
<tng-input input-only @get-amount="inputDisplay" />
<!-- use here ✨ -->
<swipe-button @on-swipe-end="handleSwipeEnd">
Send {{ amount }}
<template v-if="!isSwipeEnd" #icon>
<TwemojiLoveLetter />
</template>
<template v-else #icon>
<TwemojiFourLeafClover v-motion-pop />
</template>
</swipe-button>
</template>
<script setup lang='ts'>
const amount = ref('')
const isSwipeEnd = ref(false)
const inputDisplay = (inputAmount: string) => {
amount.value = inputAmount
}
const handleSwipeEnd = (payload: boolean) => {
if (payload){
isSwipeEnd.value = true
// or do something
}
}
</script>
output of the above code 👇
Pinjam tng-input for demo
Send
Props
Props | Type | Optional |
---|---|---|
thumbWidth | String | yes |
thumbHeight | String | yes |
Slot | Type | Optional |
---|---|---|
title | any | yes |
icon | any | yes |
event | Type | Optional |
---|---|---|
@on-swipe-end | Boolean | yes |
Implementation
view part
<button class="bg-green-600 rounded-full w-full relative">
<span :style="{opacity, fontSize}" class="absolute text-light-50 font-bold inset-0 text-center grid place-content-center">
<slot>
{{ title }}
</slot>
</span>
<div ref="container" class="w-full rounded-full">
<div ref="el" :style="{left, width, height}" >
<slot name="icon">
// icon
</slot>
</div>
</div>
</button>
logic
thanks touseSwipe
<script setup lang='ts'>
import { useSwipe } from '@vueuse/core'
import { ComputedRef } from 'vue-demi'
const props = defineProps({...})
const emit = defineEmits(['onSwipeEnd'])
const el = ref<HTMLElement | null>(null)
const container = ref<HTMLElement | null>(null)
const left = ref('0')
const opacity = ref(1)
const fontSize = ref('100%')
const containerWidth = computed(() => container.value?.offsetWidth)
const elWidth: ComputedRef<number | undefined> = computed(() => el.value?.offsetWidth)
const width = computed(() => props.thumbWidth)
const height = computed(() => props.thumbHeight)
const { lengthX: distanceX } = usePointerSwipe(el, {
onSwipe() {
if (containerWidth.value) {
if (distanceX.value < 0 && elWidth.value) {
const distance = Math.abs(distanceX.value)
left.value = `${distance}px`
opacity.value = 0.8 - distance / containerWidth.value
fontSize.value = `${100 - (distance / containerWidth.value * 20)}%`
// check if element is overflowing the container width
// if yes, stick to container end
if (distance + elWidth.value > containerWidth.value)
left.value = `${containerWidth.value - elWidth.value}px`
}
else {
left.value = '0'
opacity.value = 1
}
}
},
onSwipeEnd() {
if (distanceX.value < 0 && containerWidth.value && (Math.abs(distanceX.value) / containerWidth.value) >= 0.8) {
if (elWidth.value) left.value = `${containerWidth.value - elWidth.value}px`
opacity.value = 0
emit('onSwipeEnd', true)
}
else {
left.value = '0'
opacity.value = 1
fontSize.value = '100%'
}
},
})
</script>
Happy Vue-ing! 🎇
Made with❤by leovoon