UNPKG

@zebra-ui/swiper

Version:

专为多端设计的高性能swiper轮播组件库,支持多种复杂的 3D swiper轮播效果。

740 lines (687 loc) 18.8 kB
import type { DebugConsole, DeleteProps, NextTick, Now, GetComputedStyle, ExtractTranslateValue, CalculateCurrentPosition, GetTranslate, IsObject, IsNode, Extend, SetCSSProperty, AnimateCSSModeScroll, GetSlideTransformEl, FindElementsInElements, ElementChildren, ElementChildrenByTagIndex, ElementIsChildOf, ShowWarning, CreateElement, ElementOffset, ElementPrevAll, ElementNextAll, ElementStyle, ElementIndex, ElementParents, ElementTransitionEnd, ElementOuterSize, MakeElementsArray, GetRotateFix, IsWeb, GetWindowSize, GetRect, MatchsTouchType, SimulateRequestAnimationFrame } from '../../types/components/shared/utils' import type { SwiperInstance } from '../../types/swiper-instance' import classesToTokens from './classes-to-tokens' const debugConsole: DebugConsole = (name, ...data) => { const show: boolean = false if (show) { console.log( `%c调用${name} ==================>`, 'background: #222; color: #bada55; padding: 5px;', ...data ) } } const deleteProps: DeleteProps = (obj) => { const object = obj Object.keys(object).forEach((key) => { try { object[key] = null } catch (e) { // no getter for object } try { delete object[key] } catch (e) { // something got wrong } }) } const nextTick: NextTick = (callback, delay = 0) => { return setTimeout(callback, delay) } const now: Now = () => { return Date.now() } const getComputedStyle: GetComputedStyle = (el) => { debugConsole('getComputedStyle', el) let style if (isWeb()) { if (window.getComputedStyle) { style = window.getComputedStyle(el, null) } if (!style && (el as any).currentStyle) { style = (el as any).currentStyle } if (!style) { // eslint-disable-next-line prefer-destructuring style = el.style } } return style } const extractTranslateValue: ExtractTranslateValue = ( translate3dString, axis ) => { if (!translate3dString) { return null } const match = translate3dString.match( /translate3d\(([^,]+),([^,]+),([^,]+)\)/ ) if (!match) return null const indexMap = { x: 1, y: 2, z: 3 } const index = indexMap[axis.toLowerCase() as keyof typeof indexMap] if (index) { return parseFloat(match[index].trim()) } return null } const calculateCurrentPosition: CalculateCurrentPosition = ( startX, targetX, duration, elapsedTime ) => { if (Math.abs(targetX - startX) > 100 && elapsedTime < 50) { return Math.round(targetX) } if (elapsedTime >= duration) { return Math.round(targetX) } const totalDistance = targetX - startX const progress = Math.max(0, Math.min(1, elapsedTime / duration)) const easedProgress = progress * (2 - progress) const position = Math.round(startX + totalDistance * easedProgress) if (totalDistance > 0) { return Math.min(position, targetX) } else { return Math.max(position, targetX) } } const getTranslate: GetTranslate = (el, axis = 'x', swiper) => { debugConsole('getTranslate', el, axis) let matrix let curTransform let transformMatrix if (isWeb()) { const curStyle = getComputedStyle(el) if (window.WebKitCSSMatrix) { curTransform = curStyle?.transform || curStyle?.webkitTransform if (curTransform!.split(',').length > 6) { curTransform = curTransform! .split(', ') .map((a) => a.replace(',', '.')) .join(', ') } // Some old versions of Webkit choke when 'none' is passed; pass // empty string instead in this case transformMatrix = new window.WebKitCSSMatrix( curTransform === 'none' ? '' : curTransform ) } else { transformMatrix = // @ts-ignore curStyle?.MozTransform || // @ts-ignore curStyle?.OTransform || // @ts-ignore curStyle?.MsTransform || // @ts-ignore curStyle?.msTransform || // @ts-ignore curStyle?.transform || // @ts-ignore curStyle! .getPropertyValue('transform') .replace('translate(', 'matrix(1, 0, 0, 1,') matrix = transformMatrix.toString().split(',') } if (axis === 'x') { // Latest Chrome and webkits Fix if (window.WebKitCSSMatrix) curTransform = transformMatrix.m41 // Crazy IE10 Matrix else if (matrix.length === 16) curTransform = parseFloat(matrix[12]) // Normal Browsers else curTransform = parseFloat(matrix[4]) } if (axis === 'y') { // Latest Chrome and webkits Fix if (window.WebKitCSSMatrix) curTransform = transformMatrix.m42 // Crazy IE10 Matrix else if (matrix.length === 16) curTransform = parseFloat(matrix[13]) // Normal Browsers else curTransform = parseFloat(matrix[5]) } } else { const calcCurrent = calculateCurrentPosition( swiper.touchEventsData.currentTranslate, swiper.translate, swiper.params.speed, Date.now() - swiper.touchEventsData.lastClickTime ) curTransform = calcCurrent } return curTransform || 0 } const isObject: IsObject = (o) => { return ( typeof o === 'object' && o !== null && o.constructor && Object.prototype.toString.call(o).slice(8, -1) === 'Object' ) } const isNode: IsNode = (node) => { debugConsole('isNode', node) if (isWeb()) { if ( typeof window !== 'undefined' && typeof window.HTMLElement !== 'undefined' ) { return node instanceof HTMLElement } return node && (node.nodeType === 1 || node.nodeType === 11) } else { return false } } const extend: Extend = (...args) => { const to = Object(args[0]) const noExtend = ['__proto__', 'constructor', 'prototype'] for (let i = 1; i < args.length; i += 1) { const nextSource = args[i] if ( nextSource !== undefined && nextSource !== null && !isNode(nextSource) ) { const keysArray = Object.keys(Object(nextSource)).filter( (key) => noExtend.indexOf(key) < 0 ) for ( let nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex += 1 ) { const nextKey = keysArray[nextIndex] const desc = Object.getOwnPropertyDescriptor(nextSource, nextKey) if (desc !== undefined && desc.enumerable) { if (isObject(to[nextKey]) && isObject(nextSource[nextKey])) { if (nextSource[nextKey].__swiper__) { to[nextKey] = nextSource[nextKey] } else { extend(to[nextKey], nextSource[nextKey]) } } else if (!isObject(to[nextKey]) && isObject(nextSource[nextKey])) { to[nextKey] = {} if (nextSource[nextKey].__swiper__) { to[nextKey] = nextSource[nextKey] } else { extend(to[nextKey], nextSource[nextKey]) } } else { to[nextKey] = nextSource[nextKey] } } } } } return to } const setCSSProperty: SetCSSProperty = (el, varName, varValue) => { debugConsole('setCSSProperty', el, varName, varValue) el.style.setProperty(varName, varValue) } const animateCSSModeScroll: AnimateCSSModeScroll = ({ swiper, targetPosition, side }) => { debugConsole('animateCSSModeScroll', swiper, targetPosition, side) if (isWeb()) { const startPosition = -swiper.translate let startTime: number | null = null let time: number const duration = swiper.params.speed swiper.wrapperEl.style.scrollSnapType = 'none' window.cancelAnimationFrame(swiper.cssModeFrameID) const dir = targetPosition > startPosition ? 'next' : 'prev' const isOutOfBound = (current: number, target: number) => { return ( (dir === 'next' && current >= target) || (dir === 'prev' && current <= target) ) } const animate = () => { time = new Date().getTime() if (startTime === null) { startTime = time } const progress = Math.max(Math.min((time - startTime) / duration, 1), 0) const easeProgress = 0.5 - Math.cos(progress * Math.PI) / 2 let currentPosition = startPosition + easeProgress * (targetPosition - startPosition) if (isOutOfBound(currentPosition, targetPosition)) { currentPosition = targetPosition } swiper.wrapperEl.scrollTo({ [side]: currentPosition }) if (isOutOfBound(currentPosition, targetPosition)) { swiper.wrapperEl.style.overflow = 'hidden' swiper.wrapperEl.style.scrollSnapType = '' setTimeout(() => { swiper.wrapperEl.style.overflow = '' swiper.wrapperEl.scrollTo({ [side]: currentPosition }) }) window.cancelAnimationFrame(swiper.cssModeFrameID) return } swiper.cssModeFrameID = window.requestAnimationFrame(animate) } animate() } } const getSlideTransformEl: GetSlideTransformEl = (slideEl) => { debugConsole('getSlideTransformEl', slideEl) if (isWeb()) { return ( slideEl.querySelector('.swiper-slide-transform') || (slideEl.shadowRoot && slideEl.shadowRoot.querySelector('.swiper-slide-transform')) || slideEl ) } return slideEl } const findElementsInElements: FindElementsInElements = ( elements = [], selector = '' ) => { debugConsole('findElementsInElements', elements, selector) if (isWeb()) { const found: HTMLElement[] = [] elements.forEach((el) => { // @ts-ignore found.push(...el.querySelectorAll(selector)) }) return found } return [] } const elementChildren: ElementChildren = (element, selector = '') => { debugConsole('elementChildren', element, selector) if (isWeb()) { const children = [...element.children] if (element instanceof HTMLSlotElement) { children.push(...element.assignedElements()) } if (!selector) { return children } return children.filter((el) => el.matches(selector)) } return (element && element.children) || [] } const elementChildrenByTagIndex: ElementChildrenByTagIndex = ( element, selector ) => { debugConsole('elementChildrenByTagIndex', element, selector) return ( (element && element.children.filter( (item: SwiperInstance) => item.swiperItemIndex.value == selector )) || [] ) } const elementIsChildOf: ElementIsChildOf = (el, parent) => { debugConsole('elementIsChildOf', el, parent) if (isWeb()) { const isChild = parent.contains(el) if (!isChild && parent instanceof HTMLSlotElement) { const children = [...parent.assignedElements()] return children.includes(el) } return isChild } return true } const showWarning: ShowWarning = (text) => { try { console.warn(text) return } catch (err) { // err } } const createElement: CreateElement = (tag, classes = []) => { debugConsole('createElement', tag, classes) if (isWeb()) { const el = document.createElement(tag) el.classList.add( ...(Array.isArray(classes) ? classes : classesToTokens(classes)) ) return el } } const elementOffset: ElementOffset = (el) => { debugConsole('elementOffset', el) if (isWeb()) { const box = el.getBoundingClientRect() // eslint-disable-next-line prefer-destructuring const body = document.body const clientTop = el.clientTop || body.clientTop || 0 const clientLeft = el.clientLeft || body.clientLeft || 0 const scrollTop = el === window ? window.scrollY : el.scrollTop const scrollLeft = el === window ? window.scrollX : el.scrollLeft return { top: box.top + scrollTop - clientTop, left: box.left + scrollLeft - clientLeft } } } const elementPrevAll: ElementPrevAll = (el, selector, wrapperEl) => { debugConsole('elementPrevAll', el, selector) if (isWeb()) { const prevEls = [] while (el.previousElementSibling) { const prev = el.previousElementSibling // eslint-disable-line if (selector) { if (prev.matches(selector)) prevEls.push(prev) } else prevEls.push(prev) el = prev } return prevEls } else { return wrapperEl ? wrapperEl.children .filter( (res: any, index: number) => index < wrapperEl.children.indexOf(el) ) .reverse() : el.childrenList .filter( (res: any, index: number) => index < el.childrenList.indexOf(el) ) .reverse() } } const elementNextAll: ElementNextAll = (el, selector, wrapperEl) => { debugConsole('elementNextAll', el, selector) if (isWeb()) { const nextEls = [] while (el.nextElementSibling) { const next = el.nextElementSibling // eslint-disable-line if (selector) { if (next.matches(selector)) nextEls.push(next) } else nextEls.push(next) el = next } return nextEls } else { return wrapperEl ? wrapperEl.children.filter( (res: any, index: number) => index > wrapperEl.children.indexOf(el) ) : el.childrenList.filter( (res: any, index: number) => index > el.childrenList.indexOf(el) ) } } const elementStyle: ElementStyle = (el, prop) => { debugConsole('elementStyle', el, prop) if (isWeb()) { return window.getComputedStyle(el, null).getPropertyValue(prop) } else { return el.style.getPropertyValue(prop) } } const elementIndex: ElementIndex = (el, wrapperEl) => { debugConsole('elementIndex', el) if (isWeb()) { let child = el let i if (child) { i = 0 // eslint-disable-next-line while ((child = child.previousSibling) !== null) { if (child.nodeType === 1) i += 1 } return i } } else { if (el) { return wrapperEl ? wrapperEl.children.indexOf(el) : el.childrenList.indexOf(el) } } return undefined } const elementParents: ElementParents = (el, selector) => { debugConsole('elementParents', el, selector) if (isWeb()) { const parents = [] // eslint-disable-line let parent = el.parentElement // eslint-disable-line while (parent) { if (selector) { if (parent.matches(selector)) parents.push(parent) } else { parents.push(parent) } parent = parent.parentElement } return parents } } const elementTransitionEnd: ElementTransitionEnd = (el, callback) => { debugConsole('elementTransitionEnd', el, callback) if (isWeb()) { /* eslint-disable */ function fireCallBack(e: TransitionEvent) { if (e.target !== el) return callback.call(el, e) el.removeEventListener('transitionend', fireCallBack) } // @ts-ignore if (callback) { el.addEventListener('transitionend', fireCallBack) } } else { const regex = new RegExp( `[${['swiperWrapper', 'swiperItem'].join('')}]`, 'g' ) function fireCallBack(e: any) { if (e.target.id.toString().replace(regex, '') !== `${el.uid}`) return callback.call(el, e) el.removeEventListener( 'transitionend', // @ts-ignore fireCallBack.bind(this), 'fireCallBack' ) } // @ts-ignore if (callback) { el.addEventListener( 'transitionend', fireCallBack.bind(this), 'fireCallBack' ) } } } const elementOuterSize: ElementOuterSize = (el, size, includeMargins) => { debugConsole('elementOuterSize', el, size, includeMargins) if (isWeb()) { if (includeMargins) { return ( el[size === 'width' ? 'offsetWidth' : 'offsetHeight'] + parseFloat( window .getComputedStyle(el, null) .getPropertyValue(size === 'width' ? 'margin-right' : 'margin-top') ) + parseFloat( window .getComputedStyle(el, null) .getPropertyValue( size === 'width' ? 'margin-left' : 'margin-bottom' ) ) ) } return el.offsetWidth } else { return el.style[size] } } const makeElementsArray: MakeElementsArray = (el) => { debugConsole('makeElementsArray', el) return (Array.isArray(el) ? el : [el]).filter((e) => !!e) } const getRotateFix: GetRotateFix = (swiper) => { debugConsole('getRotateFix', swiper) return (v) => { if ( Math.abs(v) > 0 && swiper.browser && swiper.browser.need3dFix && Math.abs(v) % 90 === 0 ) { return v + 0.001 } return v } } const isWeb: IsWeb = () => { // #ifdef H5 return true // #endif // #ifndef H5 // eslint-disable-next-line no-unreachable return false // #endif } const getWindowSize: GetWindowSize = (el) => { if (!isWeb()) { // #ifdef MP-WEIXIN // @ts-ignore return uni.getWindowInfo() // #endif // eslint-disable-next-line no-unreachable // @ts-ignore return uni.getSystemInfoSync() } else { return { windowWidth: el.clientWidth, windowHeight: el.clientHeight } } } const getRect: GetRect = (instance, selector) => { return new Promise((resolve, reject) => { if (!isWeb()) { // @ts-ignore const query = uni.createSelectorQuery().in(instance.proxy) query .select(selector) .boundingClientRect((data: { width: number }) => { if (data && data.width) { resolve(data) } else { console.warn(`[ZebraSwiper] ${selector}:获取swiper节点信息失败`) } }) .exec() } }) } const matchsTouchType: MatchsTouchType = (name, type) => { const map: Record<string, string[]> = { touchstart: ['touchstart', 'touchStart', 'onTouchstart'], touchmove: ['touchmove', 'touchMove', 'onTouchmove'], touchend: ['touchend', 'touchEnd', 'onTouchend'], touchcancel: ['touchcancel', 'touchCancel', 'onTouchcancel'] } return map[name].includes(type) } // @ts-ignore const simulateRequestAnimationFrame: SimulateRequestAnimationFrame = ( callback ) => { setTimeout(() => { callback() }, 16) } export { animateCSSModeScroll, deleteProps, nextTick, now, getTranslate, isObject, extend, getComputedStyle, setCSSProperty, getSlideTransformEl, showWarning, // dom findElementsInElements, createElement, elementChildren, elementIsChildOf, elementOffset, elementPrevAll, elementNextAll, elementStyle, elementIndex, elementParents, elementTransitionEnd, elementOuterSize, makeElementsArray, getRotateFix, isWeb, getRect, getWindowSize, matchsTouchType, simulateRequestAnimationFrame, extractTranslateValue, elementChildrenByTagIndex }