提交 21457d96 编写于 作者: D DCloud_LXH

feat(h5): swiper add navigation

上级 e276e040
...@@ -11,13 +11,16 @@ import { ...@@ -11,13 +11,16 @@ import {
VNode, VNode,
markRaw, markRaw,
SetupContext, SetupContext,
watchEffect,
} from 'vue' } from 'vue'
import { extend } from '@vue/shared'
import { defineBuiltInComponent } from '../../helpers/component' import { defineBuiltInComponent } from '../../helpers/component'
import { useCustomEvent, CustomEventTrigger } from '../../helpers/useEvent' import { useCustomEvent, CustomEventTrigger } from '../../helpers/useEvent'
import { useTouchtrack } from '../../helpers/useTouchtrack' import { useTouchtrack } from '../../helpers/useTouchtrack'
import { flatVNode } from '../../helpers/flatVNode' import { flatVNode } from '../../helpers/flatVNode'
import { useRebuild } from '../../helpers/useRebuild' import { useRebuild } from '../../helpers/useRebuild'
import { rpx2px } from '@dcloudio/uni-core' import { rpx2px } from '@dcloudio/uni-core'
import { createSvgIconVNode, ICON_PATH_BACK } from '@dcloudio/uni-core'
const props = { const props = {
indicatorDots: { indicatorDots: {
...@@ -80,9 +83,22 @@ const props = { ...@@ -80,9 +83,22 @@ const props = {
type: [Boolean, String], type: [Boolean, String],
default: false, 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<keyof typeof props, any> type Props = Record<keyof typeof props, any>
type CurrentChangeSource = 'click' | 'touch' | 'autoplay' | ''
export interface SwiperContext { export interface SwiperContext {
rootRef: Ref<HTMLElement | null> rootRef: Ref<HTMLElement | null>
...@@ -154,9 +170,11 @@ function useLayout( ...@@ -154,9 +170,11 @@ function useLayout(
let transitionStart: number | null let transitionStart: number | null
let currentChangeSource = '' let currentChangeSource = ''
let animationFrame: number let animationFrame: number
const swiperEnabled: ComputedRef<boolean> = computed(
() => swiperContexts.value.length > state.displayMultipleItems
)
const circularEnabled: ComputedRef<boolean> = computed( const circularEnabled: ComputedRef<boolean> = computed(
() => () => props.circular && swiperEnabled.value
props.circular && swiperContexts.value.length > state.displayMultipleItems
) )
function checkCircularLayout(index: number) { function checkCircularLayout(index: number) {
if (!invalid) { if (!invalid) {
...@@ -276,7 +294,11 @@ function useLayout( ...@@ -276,7 +294,11 @@ function useLayout(
updateViewport(l) updateViewport(l)
animationFrame = requestAnimationFrame(animateFrameFuncProto) animationFrame = requestAnimationFrame(animateFrameFuncProto)
} }
function animateViewport(current: number, source: string, n: number) { function animateViewport(
current: number,
source: CurrentChangeSource,
n: number
) {
cancelViewportAnimation() cancelViewportAnimation()
const duration = state.duration const duration = state.duration
const length = swiperContexts.value.length const length = swiperContexts.value.length
...@@ -310,6 +332,8 @@ function useLayout( ...@@ -310,6 +332,8 @@ function useLayout(
position += length position += length
} }
} }
} else if (source === 'click') {
current = current + state.displayMultipleItems - 1 < length ? current : 0
} }
animating = { animating = {
...@@ -604,6 +628,8 @@ function useLayout( ...@@ -604,6 +628,8 @@ function useLayout(
} }
return { return {
onSwiperDotClick, onSwiperDotClick,
circularEnabled,
swiperEnabled,
} }
} }
...@@ -688,7 +714,7 @@ export default /*#__PURE__*/ defineBuiltInComponent({ ...@@ -688,7 +714,7 @@ export default /*#__PURE__*/ defineBuiltInComponent({
} }
provide('removeSwiperContext', removeSwiperContext) provide('removeSwiperContext', removeSwiperContext)
const { onSwiperDotClick } = useLayout( const { onSwiperDotClick, circularEnabled, swiperEnabled } = useLayout(
props, props,
state, state,
swiperContexts, swiperContexts,
...@@ -697,6 +723,19 @@ export default /*#__PURE__*/ defineBuiltInComponent({ ...@@ -697,6 +723,19 @@ export default /*#__PURE__*/ defineBuiltInComponent({
trigger trigger
) )
let createNavigationTsx: () => JSX.Element | null = () => null
if (__PLATFORM__ === 'h5') {
createNavigationTsx = useSwiperNavigation(
rootRef,
props,
state,
onSwiperDotClick,
swiperContexts,
circularEnabled,
swiperEnabled
)
}
return () => { return () => {
const defaultSlots = slots.default && slots.default() const defaultSlots = slots.default && slots.default()
// TODO filter // TODO filter
...@@ -745,9 +784,164 @@ export default /*#__PURE__*/ defineBuiltInComponent({ ...@@ -745,9 +784,164 @@ export default /*#__PURE__*/ defineBuiltInComponent({
))} ))}
</div> </div>
)} )}
{createNavigationTsx()}
</div> </div>
</uni-swiper> </uni-swiper>
) )
} }
}, },
}) })
type NavigationHoverType = 'over' | 'out'
type NavigationClickType = 'prev' | 'next'
const useSwiperNavigation = /*#__PURE__*/ (
rootRef: Ref<HTMLElement | null>,
props: Props,
state: State,
onSwiperDotClick: ReturnType<typeof useLayout>['onSwiperDotClick'],
swiperContext: Ref<SwiperContext[]>,
circularEnabled: ComputedRef<boolean>,
swiperEnabled: ComputedRef<boolean>
) => {
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 (
<>
<div
class="uni-swiper-navigation uni-swiper-navigation-prev"
// @ts-expect-error
class={extend(
{
'uni-swiper-navigation-disabled': prevDisabled,
},
navigationClass
)}
onClick={() => navigationClick('prev')}
{...navigationAttr}
>
{createNavigationSVG()}
</div>
<div
class="uni-swiper-navigation uni-swiper-navigation-next"
// @ts-expect-error
class={extend(
{
'uni-swiper-navigation-disabled': nextDisabled,
},
navigationClass
)}
onClick={() => navigationClick('next')}
{...navigationAttr}
>
{createNavigationSVG()}
</div>
</>
)
}
return null
}
return createNavigationTsx
}
...@@ -83,3 +83,70 @@ uni-swiper[hidden] { ...@@ -83,3 +83,70 @@ uni-swiper[hidden] {
.uni-swiper-dot-active { .uni-swiper-dot-active {
background-color: #000000; 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);
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册