@xiaohaih/drag
Version:
拖拽插件, 可通过指令或函数调用来拖拽元素移动
111 lines (106 loc) • 6.09 kB
text/typescript
import { PluginSortLevel } from '../../src/config';
import { getBoundingClientRect, getParent, getSize } from '../../src/utils/assist';
import { getEnableStatus } from '../../src/utils/index';
import type { DragCore } from '../core/index';
import type { EventOption, PluginOption } from '../core/types';
/**
* 鼠标位于边缘时, 自动滚动
*/
export function Scrolling(): PluginOption {
// 用来记录滚动的距离
let cacheInfo: [HTMLElement, { clientX: number; clientY: number; x: number; y: number; scrollContainer: HTMLElement; scrollContainerRect: DOMRect }][] = [];
return {
name: 'Scrolling',
sort: PluginSortLevel.sky,
install(ins) {
let timer = 0;
/** 滚动 */
function makeScroll(option: EventOption, ins: DragCore) {
if (!(ins.status && getEnableStatus(ins.option.scrollingOptions))) return;
const pluginOption = ins.option.scrollingOptions!;
const item = cacheInfo.find((v) => v[0] === option.target);
if (!item) return;
const [widthRatio, heightRatio] = ins.ratio;
const { threshold = 40, speed = 10, scrollOption } = pluginOption;
const _scrollOption: ScrollToOptions = { ...scrollOption };
const {
scrollWidth,
scrollHeight,
offsetWidth,
offsetHeight,
scrollTop,
scrollLeft,
clientWidth,
clientHeight,
} = item[1].scrollContainer;
if (scrollWidth <= offsetWidth && scrollHeight <= offsetHeight) return;
const { x, y } = item[1].scrollContainerRect;
// 当记录中存在滚动时, 重新计算元素的坐标
option.x = ((option.clientX - x - option.offsetInsetX) * widthRatio) + item[1].x;
option.y = ((option.clientY - y - option.offsetInsetY) * heightRatio) + item[1].y;
/** x 轴需要滚动的距离 */
const xScrollNum = option.clientX - x < threshold
? speed * -1
// 由于存在滚动条, 所以不能取滚动容器的 offsetWidth, 且该值得改为真实的大小
: (x + (clientWidth / widthRatio)) - option.clientX < threshold
? speed
: 0;
/** y 轴需要滚动的距离 */
const yScrollNum = option.clientY - y < threshold
? speed * -1
// 由于存在滚动条, 所以不能取滚动容器的 offsetWidth, 且该值得改为真实的大小
: y + (clientHeight / heightRatio) - option.clientY < threshold
? speed
: 0;
if (xScrollNum) {
// 防止滚动距离溢出容器
const realScrollNum = xScrollNum > 0
? Math.min(scrollLeft + xScrollNum, scrollWidth - clientWidth)
: Math.max(0, scrollLeft + xScrollNum);
_scrollOption.left = item[1].x = realScrollNum;
option.x = ((option.clientX - x - option.offsetInsetX) * widthRatio) + realScrollNum;
}
if (yScrollNum) {
// 防止滚动距离溢出容器
const realScrollNum = yScrollNum > 0
? Math.min(scrollTop + yScrollNum, scrollHeight - clientHeight)
: Math.max(0, scrollTop + yScrollNum);
_scrollOption.top = item[1].y = realScrollNum;
option.y = ((option.clientY - y - option.offsetInsetY) * heightRatio) + realScrollNum;
}
(_scrollOption.left !== undefined || _scrollOption.top !== undefined) && item[1].scrollContainer.scrollTo(_scrollOption);
option.setPosition(option, option.target);
item[1].clientX = option.clientX;
item[1].clientY = option.clientY;
}
/** 轮询检测翻页 */
function pollingDetection(option: EventOption, ins: DragCore) {
if (!(ins.status && getEnableStatus(ins.option.scrollingOptions))) return;
const { scrollMs = 100 } = ins.option.scrollingOptions!;
stopPollingDetection();
makeScroll(option, ins);
timer = setInterval(makeScroll, scrollMs, option, ins) as unknown as number;
}
/** 停止轮询检测 */
function stopPollingDetection() {
clearInterval(timer);
}
ins
.on('start', (option, ins) => {
if (!(ins.status && getEnableStatus(ins.option.scrollingOptions))) return;
const { container } = ins.option.scrollingOptions!;
const scrollContainer = typeof container === 'function' ? container(option) : container || getParent(option.target);
let item = cacheInfo.find((v) => v[0] === option.target);
if (!item) cacheInfo.push((item = [option.target, { clientX: 0, clientY: 0, x: 0, y: 0, scrollContainerRect: {} as DOMRect, scrollContainer: null as unknown as HTMLElement }]));
Object.assign(item[1], { clientX: option.clientX, clientY: option.clientY, x: scrollContainer.scrollLeft, y: scrollContainer.scrollTop, scrollContainerRect: scrollContainer.getBoundingClientRect(), scrollContainer });
pollingDetection(option, ins);
})
.on('move', pollingDetection)
.on('end', (option) => {
stopPollingDetection();
const item = cacheInfo.find((v) => v[0] === option.target);
item && Object.assign(item[1], { clientX: 0, clientY: 0, x: 0, y: 0 });
});
},
};
}