diff --git a/CHANGELOG.en-US.md b/CHANGELOG.en-US.md index d42e5dfe9a4..496da142e2e 100644 --- a/CHANGELOG.en-US.md +++ b/CHANGELOG.en-US.md @@ -1,5 +1,11 @@ # CHANGELOG +## Pending + +### Feats + +- `n-carousel` supports touch operation, closes [#271](https://github.com/TuSimple/naive-ui/issues/271). + ## 2.14.0 (2021-06-23) ### Breaking Changes diff --git a/CHANGELOG.zh-CN.md b/CHANGELOG.zh-CN.md index 753103e905d..f9fd625dc5d 100644 --- a/CHANGELOG.zh-CN.md +++ b/CHANGELOG.zh-CN.md @@ -1,5 +1,11 @@ # CHANGELOG +## Pending + +### Feats + +- `n-carousel` 支持触控操作,关闭 [#271](https://github.com/TuSimple/naive-ui/issues/271) + ## 2.14.0 (2021-06-23) ### Breaking Changes diff --git a/src/carousel/src/Carousel.tsx b/src/carousel/src/Carousel.tsx index d384e1a0452..fa8f66aaba9 100644 --- a/src/carousel/src/Carousel.tsx +++ b/src/carousel/src/Carousel.tsx @@ -11,6 +11,7 @@ import { onBeforeUnmount } from 'vue' import { indexMap } from 'seemly' +import { on, off } from 'evtd' import { useConfig, useTheme } from '../../_mixins' import type { ThemeProps } from '../../_mixins' import { ExtractPublicPropTypes } from '../../_utils' @@ -36,6 +37,9 @@ export default defineComponent({ const { mergedClsPrefixRef } = useConfig(props) const currentRef = ref(1) const lengthRef = { value: 1 } + const touchingRef = ref(false) + const dragOffsetRef = ref(0) + const selfElRef = ref(null) let timerId: number | null = null let inTransition = false // current from 0 to length + 1 @@ -84,9 +88,9 @@ export default defineComponent({ const nextCurrent = current === 0 ? length : current === length + 1 ? 1 : null if (nextCurrent !== null) { - target.style.transition = 'none' currentRef.value = nextCurrent void nextTick(() => { + target.style.transition = 'none' void target.offsetWidth target.style.transition = '' inTransition = false @@ -102,6 +106,57 @@ export default defineComponent({ setCurrent(current) } } + let dragStartX = 0 + let dragStartTime = 0 + let memorizedContainerWidth = 0 + function handleTouchstart (e: TouchEvent): void { + if (timerId !== null) { + window.clearInterval(timerId) + } + e.preventDefault() + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + memorizedContainerWidth = selfElRef.value!.offsetWidth + touchingRef.value = true + dragStartTime = Date.now() + dragStartX = e.touches[0].clientX + on('touchmove', document, handleTouchmove) + on('touchend', document, handleTouchend) + on('touchcancel', document, handleTouchend) + } + function handleTouchmove (e: TouchEvent): void { + const dragOffset = e.touches[0].clientX - dragStartX + dragOffsetRef.value = + dragOffset > memorizedContainerWidth + ? memorizedContainerWidth + : dragOffset < -memorizedContainerWidth + ? -memorizedContainerWidth + : dragOffset + } + function handleTouchend (): void { + if (props.autoplay) resetInterval() + void nextTick(() => { + touchingRef.value = false + }) + const { value: selfEl } = selfElRef + if (selfEl) { + const { offsetWidth } = selfEl + const { value: dragOffset } = dragOffsetRef + const duration = Date.now() - dragStartTime + // more than 50% width or faster than 0.4px per ms + if (dragOffset > offsetWidth / 2 || dragOffset / duration > 0.4) { + prev() + } else if ( + dragOffset < -offsetWidth / 2 || + dragOffset / duration < -0.4 + ) { + next() + } + } + dragOffsetRef.value = 0 + off('touchmove', document, handleTouchmove) + off('touchend', document, handleTouchend) + off('touchcancel', document, handleTouchend) + } function resetInterval (): void { if (timerId !== null) { window.clearInterval(timerId) @@ -134,13 +189,17 @@ export default defineComponent({ props ) return { + selfElRef, mergedClsPrefix: mergedClsPrefixRef, current: currentRef, lengthRef, + touching: touchingRef, + dragOffset: dragOffsetRef, prev, next, setCurrent, handleKeydown, + handleTouchstart, handleTransitionEnd, cssVars: computed(() => { const { @@ -173,14 +232,17 @@ export default defineComponent({