zarm
Version:
基于 React 的移动端UI库
316 lines (274 loc) • 9.78 kB
JavaScript
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;