UNPKG

zarm

Version:

基于 React 的移动端UI库

316 lines (274 loc) 9.78 kB
import _extends from "@babel/runtime/helpers/extends"; import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray"; import _slicedToArray from "@babel/runtime/helpers/slicedToArray"; import React, { cloneElement, Children, forwardRef, useState, useEffect, useRef, useCallback, useMemo, useImperativeHandle } from 'react'; import { createBEM } from '@zarm-design/bem'; import { useDrag } from '@use-gesture/react'; import Events from '../utils/events'; import { ConfigContext } from '../config-provider'; import { getBoundingClientRect } from '../utils/dom'; var Carousel = /*#__PURE__*/forwardRef(function (props, ref) { var _React$useContext = React.useContext(ConfigContext), prefixCls = _React$useContext.prefixCls; var bem = createBEM('carousel', { prefixCls: prefixCls }); var className = props.className, height = props.height, style = props.style, children = props.children, direction = props.direction, loop = props.loop, onChangeEnd = props.onChangeEnd, onChange = props.onChange, autoPlay = props.autoPlay, autoPlayIntervalTime = props.autoPlayIntervalTime, swipeable = props.swipeable, animationDuration = props.animationDuration, propActiveIndex = props.activeIndex, showPagination = props.showPagination, moveDistanceRatio = props.moveDistanceRatio, moveTimeSpan = props.moveTimeSpan; var stateRef = useRef({ activeIndex: propActiveIndex, activeIndexChanged: false }); var _useState = useState(stateRef.current.activeIndex), _useState2 = _slicedToArray(_useState, 2), activeIndexState = _useState2[0], setActiveIndexState = _useState2[1]; var updateRef = useRef(function (state) { stateRef.current = state; setActiveIndexState(state.activeIndex); }); var isVertical = direction === 'vertical'; var carouselRef = ref || /*#__PURE__*/React.createRef(); var carouselItemsRef = useRef(null); var translateXRef = useRef(0); var translateYRef = useRef(0); var count = useMemo(function () { return Children.count(children); }, [children]); // 处理节点(首尾拼接) var carouselItems = useMemo(function () { if (children == null || children.length === 0) { return; } // 增加头尾拼接节点 var itemList = _toConsumableArray(children); var firstItem = itemList[0]; var lastItem = itemList[itemList.length - 1]; if (loop) { itemList.push(firstItem); itemList.unshift(lastItem); } // 节点追加后重排key var newItems = React.Children.map(itemList, function (element, index) { return /*#__PURE__*/cloneElement(element, { key: index, className: bem('item', [element.props.className]) }); }); return newItems; }, [children]); // 执行过渡动画 var doTransition = useCallback(function (offset, animationDurationNum) { var dom = carouselItemsRef.current; var x = 0; var y = 0; if (!isVertical) { x = offset.x; } else { y = offset.y; } dom.style.transitionDuration = "".concat(animationDurationNum, "ms"); dom.style.transform = "translate3d(".concat(x, "px, ").concat(y, "px, 0)"); }, [isVertical]); var onMoving = useRef(false); var transitionEnd = useCallback(function () { onMoving.current = false; var _stateRef$current = stateRef.current, activeIndex = _stateRef$current.activeIndex, activeIndexChanged = _stateRef$current.activeIndexChanged; var dom = carouselItemsRef.current; var index = loop ? activeIndex + 1 : activeIndex; var size = getBoundingClientRect(dom); translateXRef.current = -size.width * index; translateYRef.current = -size.height * index; doTransition({ x: translateXRef.current, y: translateYRef.current }, 0); if (activeIndexChanged) { onChangeEnd === null || onChangeEnd === void 0 ? void 0 : onChangeEnd(activeIndex); } }, [loop, doTransition, onChangeEnd]); // 移动到指定编号 var onMoveTo = useCallback(function (index, animationDurationNum) { var dom = carouselItemsRef.current; var previousIndex = stateRef.current.activeIndex; var activeIndexChanged = previousIndex !== index; var num = loop ? 1 : 0; var size = getBoundingClientRect(dom); translateXRef.current = -size.width * (index + num); translateYRef.current = -size.height * (index + num); doTransition({ x: translateXRef.current, y: translateYRef.current }, animationDurationNum); if (index > count - 1) { index = 0; } else if (index < 0) { index = count - 1; } updateRef.current({ activeIndex: index, activeIndexChanged: activeIndexChanged }); if (activeIndexChanged) { onChange === null || onChange === void 0 ? void 0 : onChange(index); } }, [children, doTransition, loop, onChange]); // 滑动到指定编号 var _onSlideTo = useCallback(function (index) { onMoveTo(index, animationDuration); }, [onMoveTo, animationDuration]); // 静默跳到指定编号 var _onJumpTo = useCallback(function (index) { onMoveTo(index, 0); }, [onMoveTo]); // 更新窗口变化的位置偏移 var resize = useCallback(function () { _onJumpTo(stateRef.current.activeIndex); }, [_onJumpTo]); var intervalRef = useRef(); var bind = useDrag(function (state) { var activeIndex = stateRef.current.activeIndex; if (onMoving.current) { if (activeIndex <= 0) { _onJumpTo(0); } else if (activeIndex >= count - 1) { _onJumpTo(count - 1); } onMoving.current = false; } if (!state.intentional) { return false; } ; intervalRef.current && window.clearInterval(intervalRef.current); var offset = state.offset, elapsedTime = state.elapsedTime; var _offset = _slicedToArray(offset, 2), offsetX = _offset[0], offsetY = _offset[1]; var index = isVertical ? 1 : 0; if (!offset[index]) { return false; } var action = !isVertical && offsetX > 0 || isVertical && offsetY > 0 ? 'prev' : 'next'; if (!loop && (action === 'prev' && activeIndex <= 0 || action === 'next' && activeIndex >= count - 1)) { return false; } if (state.last) { var dom = carouselItemsRef.current; var size = getBoundingClientRect(dom); var ratio = !isVertical ? Math.abs(offsetX / size.width) : Math.abs(offsetY / size.height); // 判断滑动临界点 // 1.滑动距离超过0,且滑动距离和父容器长度之比超过moveDistanceRatio // 2.滑动释放时间差低于moveTimeSpan if (ratio >= moveDistanceRatio || elapsedTime <= moveTimeSpan) { activeIndex = action === 'next' ? activeIndex + 1 : activeIndex - 1; } if (loop && (activeIndex >= count - 1 || activeIndex <= 1)) { onMoving.current = true; } _onSlideTo(activeIndex); return false; } doTransition({ x: translateXRef.current + offset[0], y: translateYRef.current + offset[1] }, 0); }, { from: function from() { return [0, 0]; }, enabled: swipeable, axis: isVertical ? 'y' : 'x', pointer: { touch: true }, preventScroll: !isVertical, triggerAllEvents: true }); useEffect(function () { if (!autoPlay || count <= 1) return; intervalRef.current = window.setInterval(function () { !onMoving.current && _onSlideTo(stateRef.current.activeIndex + 1); }, autoPlayIntervalTime); return function () { window.clearInterval(intervalRef.current); }; }, [autoPlay, autoPlayIntervalTime, loop, _onSlideTo, stateRef.current.activeIndex]); useEffect(function () { // 监听窗口变化 Events.on(window, 'resize', resize); // 设置起始位置编号 _onJumpTo(propActiveIndex); return function () { // 移除监听窗口变化 Events.off(window, 'resize', resize); }; }, [_onJumpTo, _onSlideTo, propActiveIndex, resize, transitionEnd]); useImperativeHandle(carouselRef, function () { return { onJumpTo: function onJumpTo(index) { _onJumpTo(index); }, onSlideTo: function onSlideTo(index) { _onSlideTo(index); } }; }); var pagination = useMemo(function () { if (!showPagination) return null; var paginationItems = Children.map(children, function (_child, index) { return /*#__PURE__*/React.createElement("div", { key: "pagination-".concat(+index), className: bem('pagination__item', [{ active: index === activeIndexState }]), onClick: function onClick() { return _onSlideTo(index); } }); }); return /*#__PURE__*/React.createElement("div", { className: bem('pagination') }, paginationItems); }, [showPagination, children, activeIndexState]); var cls = bem([{ horizontal: !isVertical, vertical: isVertical }, className]); var itemsStyle = {}; if (isVertical) { itemsStyle.height = height; } return /*#__PURE__*/React.createElement("div", _extends({ className: cls, style: style, ref: carouselRef }, bind()), /*#__PURE__*/React.createElement("div", { ref: carouselItemsRef, className: bem('items'), onTransitionEnd: transitionEnd, style: itemsStyle }, carouselItems), pagination); }); Carousel.displayName = 'Carousel'; Carousel.defaultProps = { direction: 'horizontal', height: 160, loop: false, activeIndex: 0, animationDuration: 500, swipeable: true, autoPlay: false, autoPlayIntervalTime: 3000, moveDistanceRatio: 0.5, moveTimeSpan: 300, showPagination: true }; export default Carousel;