From 21457d96b842074c5699b79e78dddc0803eaf8ad Mon Sep 17 00:00:00 2001 From: DCloud_LXH <283700113@qq.com> Date: Fri, 16 Sep 2022 15:52:32 +0800 Subject: [PATCH] feat(h5): swiper add navigation --- .../uni-components/src/vue/swiper/index.tsx | 202 +++++++++++++++++- packages/uni-components/style/swiper.css | 67 ++++++ 2 files changed, 265 insertions(+), 4 deletions(-) diff --git a/packages/uni-components/src/vue/swiper/index.tsx b/packages/uni-components/src/vue/swiper/index.tsx index 0636fe426..4d5012b54 100644 --- a/packages/uni-components/src/vue/swiper/index.tsx +++ b/packages/uni-components/src/vue/swiper/index.tsx @@ -11,13 +11,16 @@ import { VNode, markRaw, SetupContext, + watchEffect, } from 'vue' +import { extend } from '@vue/shared' import { defineBuiltInComponent } from '../../helpers/component' import { useCustomEvent, CustomEventTrigger } from '../../helpers/useEvent' import { useTouchtrack } from '../../helpers/useTouchtrack' import { flatVNode } from '../../helpers/flatVNode' import { useRebuild } from '../../helpers/useRebuild' import { rpx2px } from '@dcloudio/uni-core' +import { createSvgIconVNode, ICON_PATH_BACK } from '@dcloudio/uni-core' const props = { indicatorDots: { @@ -80,9 +83,22 @@ const props = { type: [Boolean, String], default: false, }, + navigation: { + type: [Boolean, String], + default: false, + }, + navigationColor: { + type: String, + default: '#fff', + }, + navigationActiveColor: { + type: String, + default: 'rgba(53, 53, 53, 0.6)', + }, } type Props = Record +type CurrentChangeSource = 'click' | 'touch' | 'autoplay' | '' export interface SwiperContext { rootRef: Ref @@ -154,9 +170,11 @@ function useLayout( let transitionStart: number | null let currentChangeSource = '' let animationFrame: number + const swiperEnabled: ComputedRef = computed( + () => swiperContexts.value.length > state.displayMultipleItems + ) const circularEnabled: ComputedRef = computed( - () => - props.circular && swiperContexts.value.length > state.displayMultipleItems + () => props.circular && swiperEnabled.value ) function checkCircularLayout(index: number) { if (!invalid) { @@ -276,7 +294,11 @@ function useLayout( updateViewport(l) animationFrame = requestAnimationFrame(animateFrameFuncProto) } - function animateViewport(current: number, source: string, n: number) { + function animateViewport( + current: number, + source: CurrentChangeSource, + n: number + ) { cancelViewportAnimation() const duration = state.duration const length = swiperContexts.value.length @@ -310,6 +332,8 @@ function useLayout( position += length } } + } else if (source === 'click') { + current = current + state.displayMultipleItems - 1 < length ? current : 0 } animating = { @@ -604,6 +628,8 @@ function useLayout( } return { onSwiperDotClick, + circularEnabled, + swiperEnabled, } } @@ -688,7 +714,7 @@ export default /*#__PURE__*/ defineBuiltInComponent({ } provide('removeSwiperContext', removeSwiperContext) - const { onSwiperDotClick } = useLayout( + const { onSwiperDotClick, circularEnabled, swiperEnabled } = useLayout( props, state, swiperContexts, @@ -697,6 +723,19 @@ export default /*#__PURE__*/ defineBuiltInComponent({ trigger ) + let createNavigationTsx: () => JSX.Element | null = () => null + if (__PLATFORM__ === 'h5') { + createNavigationTsx = useSwiperNavigation( + rootRef, + props, + state, + onSwiperDotClick, + swiperContexts, + circularEnabled, + swiperEnabled + ) + } + return () => { const defaultSlots = slots.default && slots.default() // TODO filter @@ -745,9 +784,164 @@ export default /*#__PURE__*/ defineBuiltInComponent({ ))} )} + {createNavigationTsx()} ) } }, }) + +type NavigationHoverType = 'over' | 'out' +type NavigationClickType = 'prev' | 'next' + +const useSwiperNavigation = /*#__PURE__*/ ( + rootRef: Ref, + props: Props, + state: State, + onSwiperDotClick: ReturnType['onSwiperDotClick'], + swiperContext: Ref, + circularEnabled: ComputedRef, + swiperEnabled: ComputedRef +) => { + let isNavigationAuto = false + let prevDisabled = false + let nextDisabled = false + let hideNavigation = ref(false) + + watchEffect(() => { + isNavigationAuto = props.navigation === 'auto' + hideNavigation.value = props.navigation !== true || isNavigationAuto + swiperAddMouseEvent() + }) + + watchEffect(() => { + const swiperItemLength = swiperContext.value.length + const notCircular = !circularEnabled.value + + prevDisabled = state.current === 0 && notCircular + nextDisabled = + (state.current === swiperItemLength - 1 && notCircular) || + (notCircular && + state.current + state.displayMultipleItems >= swiperItemLength) + + if (!swiperEnabled.value) { + prevDisabled = true + nextDisabled = true + isNavigationAuto && (hideNavigation.value = true) + } + }) + + function navigationHover(event: MouseEvent, type: NavigationHoverType) { + const target = event.currentTarget + if (!target) return + ;(target as HTMLDivElement).style.backgroundColor = + type === 'over' ? props.navigationActiveColor : '' + } + const navigationAttr = { + onMouseover: (event: MouseEvent) => navigationHover(event, 'over'), + onMouseout: (event: MouseEvent) => navigationHover(event, 'out'), + } + + function navigationClick(type: NavigationClickType) { + const swiperItemLength = swiperContext.value.length + let _current = state.current + + switch (type) { + case 'prev': + _current-- + if (_current < 0 && circularEnabled.value) { + _current = swiperItemLength - 1 + } + break + case 'next': + _current++ + if (_current >= swiperItemLength && circularEnabled.value) { + _current = 0 + } + break + } + + onSwiperDotClick(_current) + } + + const createNavigationSVG = () => + createSvgIconVNode(ICON_PATH_BACK, props.navigationColor, 26) + + const _mouseMove = (e: MouseEvent) => { + const { clientX, clientY } = e + const { left, right, top, bottom, width, height } = + rootRef.value!.getBoundingClientRect() + + if (props.vertical) { + hideNavigation.value = !( + clientY - top < height / 3 || bottom - clientY < height / 3 + ) + } else { + hideNavigation.value = !( + clientX - left < width / 3 || right - clientX < width / 3 + ) + } + } + const _mouseOut = () => { + hideNavigation.value = true + } + function swiperAddMouseEvent() { + if (rootRef.value) { + rootRef.value.removeEventListener('mousemove', _mouseMove) + rootRef.value.removeEventListener('mouseout', _mouseOut) + + if (isNavigationAuto) { + rootRef.value.addEventListener('mousemove', _mouseMove) + rootRef.value.addEventListener('mouseout', _mouseOut) + } + } + } + + onMounted(swiperAddMouseEvent) + + function createNavigationTsx() { + const navigationClass = { + 'uni-swiper-navigation-hide': hideNavigation.value, + 'uni-swiper-navigation-vertical': props.vertical, + } + + if (props.navigation) { + return ( + <> +
navigationClick('prev')} + {...navigationAttr} + > + {createNavigationSVG()} +
+
navigationClick('next')} + {...navigationAttr} + > + {createNavigationSVG()} +
+ + ) + } + return null + } + + return createNavigationTsx +} diff --git a/packages/uni-components/style/swiper.css b/packages/uni-components/style/swiper.css index ce98a1a00..ef28d38ba 100644 --- a/packages/uni-components/style/swiper.css +++ b/packages/uni-components/style/swiper.css @@ -83,3 +83,70 @@ uni-swiper[hidden] { .uni-swiper-dot-active { background-color: #000000; } + +.uni-swiper-navigation { + width: 26px; + height: 26px; + cursor: pointer; + position: absolute; + top: 50%; + margin-top: -13px; + display: flex; + align-items: center; + transition: all 0.2s; + border-radius: 50%; + opacity: 1; +} + +.uni-swiper-navigation-disabled { + opacity: 0.35; + cursor: auto; + pointer-events: none; +} + +.uni-swiper-navigation-hide { + opacity: 0; + cursor: auto; + pointer-events: none; +} + +.uni-swiper-navigation-prev { + left: 10px; +} + +.uni-swiper-navigation-prev svg { + margin-left: -1px; + left: 10px; +} + +.uni-swiper-navigation-prev.uni-swiper-navigation-vertical { + top: 18px; + left: 50%; + margin-left: -13px; +} + +.uni-swiper-navigation-prev.uni-swiper-navigation-vertical svg { + transform: rotate(90deg); + margin-left: auto; + margin-top: -2px; +} + +.uni-swiper-navigation-next { + right: 10px; +} + +.uni-swiper-navigation-next svg { + transform: rotate(180deg); +} + +.uni-swiper-navigation-next.uni-swiper-navigation-vertical { + top: auto; + bottom: 5px; + left: 50%; + margin-left: -13px; +} + +.uni-swiper-navigation-next.uni-swiper-navigation-vertical svg { + margin-top: 2px; + transform: rotate(270deg); +} -- GitLab