vue3-seamless-scroll
Version:
1,898 lines • 57.1 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('vue')) :
typeof define === 'function' && define.amd ? define(['exports', 'vue'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Vue3SeamlessScroll = {}, global.Vue));
})(this, (function (exports, vue) { 'use strict';
function throttle(delay, func) {
let lastCall = 0;
return function (...args) {
const now = Date.now();
if (now - lastCall >= delay) {
lastCall = now;
func.apply(this, args);
}
};
}
function generateUUID() {
const template = 'xxxxxxxxxxxxaxxxyxxxxxxxxxxxxxxx';
return template.replace(/[xy]/g, function (char) {
const random = Math.random() * 16 | 0;
const value = char === 'x' ? random : (random & 0x3) | 0x8;
return value.toString(16);
});
}
const uuid = () => {
return generateUUID().replaceAll('-', '');
};
function duplicateId(list) {
const seen = new Set();
const duplicates = new Set();
list.forEach(item => {
if (seen.has(item.id)) {
duplicates.add(item);
} else {
seen.add(item.id);
}
});
if (duplicates.size > 0) {
list.forEach(item => {
item.id = uuid();
});
}
return list;
}
/**
*
* @param {Array} list
* @returns {Array}
*/
function listMap(list) {
return list.map((data, index) => {
return {
id: uuid(),
index,
data
}
})
}
var script$2 = vue.defineComponent({
name: 'VerticalScroll',
emits: ['offset', 'count'],
inheritAttrs: false,
props: {
// 是否开启自动滚动
modelValue: {
type: Boolean,
default: true,
},
list: {
type: Array,
required: true,
},
// 是否开启鼠标悬停
hover: {
type: Boolean,
default: false,
},
// 单步停止等待时间 (默认值 1000ms)
singleWaitTime: {
type: Number,
default: 1000,
},
// 开启鼠标悬停时支持滚轮滚动
wheel: {
type: Boolean,
default: false,
},
// 启用单行滚动
singleLine: {
type: Boolean,
default: false,
},
step: {
type: Number,
default: 0.5,
},
visibleCount: {
type: Number,
},
ease: {
type: String,
default: 'cubic-bezier(0.03, 0.76, 1, 0.16)'
},
direction: {
type: String,
default: 'up',
},
delay: {
type: Number,
default: 0,
}
},
setup(props, { expose, emit }) {
/**
* @type {import('vue').Ref<HTMLDivElement>}
*/
const realWrapperRef = vue.ref(null);
/**
* @type {import('vue').Ref<HTMLDivElement>}
*/
const realWrapperHiddenRef = vue.ref(null);
const funArgs = vue.ref([]);
const offset = vue.ref(0);
const isHover = vue.ref(false);
const direction = vue.ref(props.direction);
const isScroll = vue.computed(() => props.hover ? !isHover.value && props.modelValue : props.modelValue);
const testList = vue.ref([]);
const targetList = listMap(props.list);
const visibleCount = vue.ref(props.visibleCount === (undefined) ? 0 : props.visibleCount);
let total = 0;
let count = 0;
let totalCount = 0;
let childrenHeightList = [];
let bufferTotalHeight = 0;
let bufferSize = 0;
let cursorIndex = -1;
let realBoxHeight = 0;
let reqFrame = null;
let singleState = false;
let singleOffset = 0;
let tempOffset = 0;
let listCanScroll = false;
const cancle = () => {
cancelAnimationFrame(reqFrame);
reqFrame = null;
};
const updateCursorIndex = () => {
const totalIndex = cursorIndex + bufferSize;
if (totalIndex >= targetList.length) {
const tempIndex = totalIndex - targetList.length;
const tempFuns = ['splice',
[0, bufferSize],
[cursorIndex],
[0, tempIndex]];
funArgs.value = tempFuns;
if (tempIndex > targetList.length) {
cursorIndex = targetList.length;
} else {
cursorIndex = tempIndex;
}
} else {
const tempFuns = ['splice', [0, bufferSize], [cursorIndex, totalIndex]];
funArgs.value = tempFuns;
cursorIndex = totalIndex;
}
};
const initHeight = () => {
const children = Array.from(realWrapperRef.value.children);
const subChildren = direction.value === 'up' ?
children.slice(0, bufferSize) :
children.slice(visibleItems.value.length - bufferSize, visibleItems.value.length);
childrenHeightList = subChildren.map((c) => c.offsetHeight);
bufferTotalHeight = childrenHeightList
.reduce((a, b) => a + b, 0);
};
const animation = (isWheel, step) => {
cancle();
if (!listCanScroll) {
return;
}
const scrollFun = () => {
const singleHeight = childrenHeightList[0];
let singleDone = false;
if (singleOffset >= singleHeight) {
singleDone = true;
}
if (singleDone) {
childrenHeightList.shift();
singleOffset = 0;
count += 1;
}
if (count === total) {
totalCount += 1;
emit('count', totalCount);
count = 0;
}
if (props.singleLine && singleDone) {
singleState = true;
setTimeout(() => {
singleState = false;
if (!isWheel) {
animation(false, props.step);
}
}, props.singleWaitTime);
} else {
if (!isWheel) {
animation(false, props.step);
}
}
};
if (isScroll.value && !singleState || isWheel) {
reqFrame = requestAnimationFrame(() => {
tempOffset += step;
singleOffset += step;
if (direction.value === 'up') {
offset.value += step;
} else {
offset.value -= step;
}
if (tempOffset > bufferTotalHeight) {
emit('offset', bufferSize, targetList);
updateCursorIndex();
vue.nextTick(() => {
offset.value = direction.value === 'up' ? 0 : getFullHeight() - realBoxHeight;
tempOffset = 0;
initHeight();
scrollFun();
});
} else {
scrollFun();
}
});
}
};
const onMouseenter = () => {
isHover.value = true;
};
const onMouseleave = () => {
isHover.value = false;
if (props.hover) {
animation(false, props.step);
}
};
const throttleFunc = throttle(30, (e) => {
animation(true, 10);
});
const onWheel = (e) => {
if (props.hover && props.wheel) {
throttleFunc(e);
e.preventDefault();
e.stopPropagation();
}
};
/**
* @type {import('vue').ComputedRef<Array<any>>}
*/
const visibleItems = vue.computed(() => {
let tempList = [];
if (funArgs.value.length === 0) {
tempList = targetList.slice(0, visibleCount.value);
} else if (funArgs.value[0] === 'splice') {
tempList = direction.value === 'up' ? visibleItems.value : visibleItems.value.reverse();
tempList.splice(...funArgs.value[1]);
funArgs.value.slice(2).forEach(args => {
tempList.push(...targetList.slice(...args));
});
} else {
funArgs.value.slice(1).forEach(args => {
tempList.push(...targetList.slice(...args));
});
}
if (!(direction.value === 'up')) {
tempList.reverse();
}
return duplicateId(tempList);
});
const initCursorIndex = () => {
cursorIndex = visibleCount.value + bufferSize;
if (cursorIndex >= targetList.length) {
const tempIndex = cursorIndex - targetList.length;
const tempFunArgs = ['slice', [0, cursorIndex], [0, tempIndex]];
if (tempIndex > targetList.length) {
cursorIndex = targetList.length;
} else {
cursorIndex = tempIndex;
}
return tempFunArgs;
} else {
return ['slice', [0, cursorIndex]];
}
};
const getFullHeight = () => {
return Array.from(realWrapperRef.value.children)
.map((c) => c.offsetHeight).reduce((a, b) => a + b, 0);
};
const initVisibleCount = (cb) => {
if (props.visibleCount === (undefined)) {
testList.value = listMap([props.list[0]]);
vue.nextTick(() => {
visibleCount.value = Math.ceil(realBoxHeight / realWrapperHiddenRef.value.offsetHeight) + 2;
testList.value = targetList.slice(0, visibleCount.value);
vue.nextTick(() => {
cb();
});
});
} else {
testList.value = targetList.slice(0, visibleCount.value);
vue.nextTick(() => {
cb();
});
}
};
vue.onMounted(() => {
if (!!realWrapperRef.value) {
realWrapperRef.value.parentElement.addEventListener('mouseenter', onMouseenter);
realWrapperRef.value.parentElement.addEventListener('mouseleave', onMouseleave);
realWrapperRef.value.parentElement.addEventListener('wheel', onWheel);
realBoxHeight = realWrapperRef.value.parentElement.offsetHeight;
if (props.list.length > 0) {
initVisibleCount(() => {
const hasVerticalScroll = realWrapperHiddenRef.value.offsetHeight > realBoxHeight;
if (hasVerticalScroll) {
bufferSize = getBufferSize();
funArgs.value = initCursorIndex();
vue.nextTick(() => {
offset.value = direction.value === 'up' ? 0 : getFullHeight() - realBoxHeight;
initHeight();
listCanScroll = true;
setTimeout(() => {
animation(false, props.step);
}, props.delay);
});
} else {
init();
}
testList.value = [];
});
}
}
total = targetList.length;
});
const init = () => {
count = 0;
totalCount = 0;
listCanScroll = false;
bufferSize = 0;
funArgs.value = [];
vue.nextTick(() => {
offset.value = direction.value === 'up' ? 0 : getFullHeight() - realBoxHeight;
tempOffset = 0;
singleOffset = 0;
});
};
const getBufferSize = () => {
let tempBufferSize = targetList.length - visibleCount.value;
tempBufferSize = Math.max(1, tempBufferSize);
tempBufferSize = Math.min(5, tempBufferSize);
return tempBufferSize;
};
const add = (index, values, cb) => {
if (!!values && values.length > 0) {
if (index > targetList.length) {
index = targetList.length;
}
if (index < 0) {
index = 0;
}
const findIndexs = [];
if (values.length === 1) {
visibleItems.value.forEach((v, i) => {
if (v.index === index) {
findIndexs.push(i);
}
});
}
const datas = [];
values.forEach((v) => {
datas.push({
id: uuid(),
data: v
});
});
targetList.splice(index, 0, ...datas);
targetList.forEach((v, i) => {
v.index = i;
});
const tempBufferSize = getBufferSize();
if (listCanScroll) {
if (index < cursorIndex) {
cursorIndex += 1;
if (cursorIndex > targetList.length) {
cursorIndex = 0;
}
}
if (values.length === 1 && findIndexs.length > 0) {
findIndexs.forEach((i) => {
visibleItems.value[i] = datas[0];
});
}
if (tempBufferSize !== bufferSize) {
bufferSize = tempBufferSize;
}
} else {
const fun = () => {
const hasVerticalScroll = realWrapperHiddenRef.value.offsetHeight > realBoxHeight;
if (hasVerticalScroll) {
bufferSize = tempBufferSize;
funArgs.value = initCursorIndex();
vue.nextTick(() => {
offset.value = direction.value === 'up' ? 0 : getFullHeight() - realBoxHeight;
tempOffset = 0;
singleOffset = 0;
initHeight();
listCanScroll = true;
animation(false, props.step);
});
} else {
if (props.visibleCount === (undefined)) {
visibleCount.value = 0;
}
init();
}
testList.value = [];
};
if (visibleCount.value === 0 && props.visibleCount === (undefined)) {
initVisibleCount(() => {
fun();
});
} else {
testList.value = targetList.slice(0, visibleCount.value);
vue.nextTick(() => {
fun();
});
}
}
}
if (!!cb && typeof cb === 'function') {
cb(targetList);
}
total = targetList.length;
};
const remove = (index, num = 1, cb) => {
if (index >= 0 && index < targetList.length) {
targetList.splice(index, num);
if (listCanScroll) {
testList.value = targetList.slice(0, visibleCount.value);
vue.nextTick(() => {
const hasVerticalScroll = realWrapperHiddenRef.value.offsetHeight > realBoxHeight;
if (hasVerticalScroll) {
const tempBufferSize = getBufferSize();
if (tempBufferSize !== bufferSize) {
bufferSize = tempBufferSize;
}
funArgs.value = initCursorIndex();
} else {
init();
}
testList.value = [];
});
} else {
init();
}
}
if (!!cb && typeof cb === 'function') {
cb(targetList);
}
total = targetList.length;
};
const reset = () => {
vue.nextTick(() => {
testList.value = targetList.slice(0, visibleCount.value);
vue.nextTick(() => {
const hasVerticalScroll = realWrapperHiddenRef.value.offsetHeight > realBoxHeight;
if (hasVerticalScroll) {
if (funArgs.value[0] === 'splice') {
updateCursorIndex();
vue.nextTick(() => {
initHeight();
});
} else {
funArgs.value = initCursorIndex();
vue.nextTick(() => {
initHeight();
});
}
} else {
init();
}
});
});
};
const update = (index, value, cb) => {
if (index >= 0 && index < targetList.length) {
const findIndexs = [];
visibleItems.value.forEach((v, i) => {
if (v.index === index) {
findIndexs.push(i);
}
});
const data = {
id: uuid(),
index: index,
data: value
};
targetList[index] = data;
if (findIndexs.length > 0) {
findIndexs.forEach((i) => {
visibleItems.value[i] = data;
});
}
}
if (!!cb && typeof cb === 'function') {
cb(targetList);
}
};
expose({ add, remove, update, reset });
vue.watch(() => props.modelValue, (val) => {
if (val) {
animation(false, props.step);
}
});
vue.onUnmounted(() => {
cancelAnimationFrame(reqFrame);
if (realWrapperRef.value) {
realWrapperRef.value.parentElement.removeEventListener('mouseenter', onMouseenter);
realWrapperRef.value.parentElement.removeEventListener('mouseleave', onMouseleave);
realWrapperRef.value.parentElement.removeEventListener('wheel', onWheel);
}
});
return {
realWrapperRef,
visibleItems,
offset,
testList,
realWrapperHiddenRef
}
}
});
const _hoisted_1 = {
style: {"position":"absolute !important","top":"-999999px !important"},
ref: "realWrapperHiddenRef"
};
function render$2(_ctx, _cache, $props, $setup, $data, $options) {
return (vue.openBlock(), vue.createElementBlock(vue.Fragment, null, [
vue.createElementVNode("div", {
class: "vue3-seamless-vertical-wrapper",
ref: "realWrapperRef",
style: vue.normalizeStyle({ transition: `transform ${_ctx.ease}`, transform: `translateY(-${_ctx.offset}px)` })
}, [
(vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(_ctx.visibleItems, (item) => {
return vue.renderSlot(_ctx.$slots, "default", {
key: item.id,
data: item.data,
index: item.index
})
}), 128 /* KEYED_FRAGMENT */))
], 4 /* STYLE */),
vue.createElementVNode("div", _hoisted_1, [
(vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(_ctx.testList, (item) => {
return vue.renderSlot(_ctx.$slots, "default", {
key: item.id,
data: item.data,
index: item.index
})
}), 128 /* KEYED_FRAGMENT */))
], 512 /* NEED_PATCH */)
], 64 /* STABLE_FRAGMENT */))
}
function styleInject(css, ref) {
if ( ref === undefined ) ref = {};
var insertAt = ref.insertAt;
if (!css || typeof document === 'undefined') { return; }
var head = document.head || document.getElementsByTagName('head')[0];
var style = document.createElement('style');
style.type = 'text/css';
if (insertAt === 'top') {
if (head.firstChild) {
head.insertBefore(style, head.firstChild);
} else {
head.appendChild(style);
}
} else {
head.appendChild(style);
}
if (style.styleSheet) {
style.styleSheet.cssText = css;
} else {
style.appendChild(document.createTextNode(css));
}
}
var css_248z$2 = "\n.vue3-seamless-vertical-wrapper[data-v-70134dc7] {\r\n width: 100%;\n}\r\n";
styleInject(css_248z$2);
script$2.render = render$2;
script$2.__scopeId = "data-v-70134dc7";
script$2.__file = "packages/VerticalScroll.vue";
var script$1 = vue.defineComponent({
name: 'HorizontalScroll',
emits: ['offset', 'count'],
inheritAttrs: false,
props: {
// 是否开启自动滚动
modelValue: {
type: Boolean,
default: true,
},
list: {
type: Array,
required: true,
},
// 是否开启鼠标悬停
hover: {
type: Boolean,
default: false,
},
// 单步停止等待时间 (默认值 1000ms)
singleWaitTime: {
type: Number,
default: 1000,
},
// 开启鼠标悬停时支持滚轮滚动
wheel: {
type: Boolean,
default: false,
},
// 启用单行滚动
singleLine: {
type: Boolean,
default: false,
},
step: {
type: Number,
default: 0.5,
},
visibleCount: {
type: Number,
},
ease: {
type: String,
default: 'cubic-bezier(0.03, 0.76, 1, 0.16)'
},
direction: {
type: String,
default: 'left',
},
delay: {
type: Number,
default: 0,
}
},
setup(props, { emit, expose }) {
/**
* @type {import('vue').Ref<HTMLDivElement>}
*/
const realWrapperRef = vue.ref(null);
/**
* @type {import('vue').Ref<HTMLDivElement>}
*/
const realWrapperHiddenRef = vue.ref(null);
const funArgs = vue.ref([]);
const offset = vue.ref(0);
const isHover = vue.ref(false);
const direction = vue.ref(props.direction);
const isScroll = vue.computed(() => props.hover ? !isHover.value && props.modelValue : props.modelValue);
const testList = vue.ref([]);
const visibleCount = vue.ref(props.visibleCount === (undefined) ? 0 : props.visibleCount);
const targetList = listMap(props.list);
let total = 0;
let count = 0;
let totalCount = 0;
let childrenWidthList = [];
let bufferTotalWidth = 0;
let bufferSize = 0;
let cursorIndex = -1;
let realBoxWidth = 0;
let reqFrame = null;
let singleState = false;
let singleOffset = 0;
let tempOffset = 0;
let listCanScroll = false;
const cancle = () => {
cancelAnimationFrame(reqFrame);
reqFrame = null;
};
const updateCursorIndex = () => {
const totalIndex = cursorIndex + bufferSize;
if (totalIndex >= targetList.length) {
const tempIndex = totalIndex - targetList.length;
const tempFuns = ['splice',
[0, bufferSize],
[cursorIndex],
[0, tempIndex]];
funArgs.value = tempFuns;
if (tempIndex > targetList.length) {
cursorIndex = targetList.length;
} else {
cursorIndex = tempIndex;
}
} else {
const tempFuns = ['splice', [0, bufferSize], [cursorIndex, totalIndex]];
funArgs.value = tempFuns;
cursorIndex = totalIndex;
}
};
const initWidth = () => {
const children = Array.from(realWrapperRef.value.children);
const subChildren = direction.value === 'left' ?
children.slice(0, bufferSize) :
children.slice(visibleItems.value.length - bufferSize, visibleItems.value.length);
childrenWidthList = subChildren.map((c) => c.offsetWidth);
bufferTotalWidth = childrenWidthList
.reduce((a, b) => a + b, 0);
};
const animation = (isWheel, step) => {
cancle();
if (!listCanScroll) {
return;
}
const scrollFun = () => {
const singleWidth = childrenWidthList[0];
let singleDone = false;
if (singleOffset >= singleWidth) {
singleDone = true;
}
if (singleDone) {
childrenWidthList.shift();
singleOffset = 0;
count += 1;
}
if (count === total) {
totalCount += 1;
emit('count', totalCount);
count = 0;
}
if (props.singleLine && singleDone) {
singleState = true;
setTimeout(() => {
singleState = false;
if (!isWheel) {
animation(false, props.step);
}
}, props.singleWaitTime);
} else {
if (!isWheel) {
animation(false, props.step);
}
}
};
if (isScroll.value && !singleState || isWheel) {
reqFrame = requestAnimationFrame(() => {
tempOffset += step;
singleOffset += step;
if (props) {
offset.value += step;
} else {
offset.value -= step;
}
if (tempOffset > bufferTotalWidth) {
emit('offset', bufferSize, targetList);
updateCursorIndex();
vue.nextTick(() => {
offset.value = direction.value === 'left' ? 0 : getFullWidth() - realBoxWidth;
tempOffset = 0;
initWidth();
scrollFun();
});
} else {
scrollFun();
}
});
}
};
const onMouseenter = () => {
isHover.value = true;
};
const onMouseleave = () => {
isHover.value = false;
if (props.hover) {
animation(false, props.step);
}
};
const throttleFunc = throttle(30, (e) => {
animation(true, 10);
});
const onWheel = (e) => {
if (props.hover && props.wheel) {
throttleFunc(e);
e.preventDefault();
e.stopPropagation();
}
};
/**
* @type {import('vue').ComputedRef<Array<any>>}
*/
const visibleItems = vue.computed(() => {
let tempList = [];
if (funArgs.value.length === 0) {
tempList = targetList.slice(0, visibleCount.value);
} else if (funArgs.value[0] === 'splice') {
tempList = direction.value === 'left' ? visibleItems.value : visibleItems.value.reverse();
tempList.splice(...funArgs.value[1]);
funArgs.value.slice(2).forEach(args => {
tempList.push(...targetList.slice(...args));
});
} else {
funArgs.value.slice(1).forEach(args => {
tempList.push(...targetList.slice(...args));
});
}
if (!(direction.value === 'left')) {
tempList.reverse();
}
return duplicateId(tempList);
});
const initCursorIndex = () => {
cursorIndex = visibleCount.value + bufferSize;
if (cursorIndex >= targetList.length) {
const tempIndex = cursorIndex - targetList.length;
const tempFunArgs = ['slice', [0, cursorIndex], [0, tempIndex]];
if (tempIndex > targetList.length) {
cursorIndex = targetList.length;
} else {
cursorIndex = tempIndex;
}
return tempFunArgs;
} else {
return ['slice', [0, cursorIndex]];
}
};
const getFullWidth = () => {
return Array.from(realWrapperRef.value.children)
.map((c) => c.offsetWidth).reduce((a, b) => a + b, 0);
};
const initVisibleCount = (cb) => {
if (props.visibleCount === (undefined)) {
testList.value = listMap([props.list[0]]);
vue.nextTick(() => {
visibleCount.value = Math.ceil(realBoxWidth / realWrapperHiddenRef.value.offsetWidth) + 2;
testList.value = targetList.slice(0, visibleCount.value);
vue.nextTick(() => {
cb();
});
});
} else {
testList.value = targetList.slice(0, visibleCount.value);
vue.nextTick(() => {
cb();
});
}
};
vue.onMounted(() => {
if (!!realWrapperRef.value) {
realWrapperRef.value.parentElement.addEventListener('mouseenter', onMouseenter);
realWrapperRef.value.parentElement.addEventListener('mouseleave', onMouseleave);
realWrapperRef.value.parentElement.addEventListener('wheel', onWheel);
realBoxWidth = realWrapperRef.value.parentElement.offsetWidth;
if (props.list.length > 0) {
initVisibleCount(() => {
const hasVerticalScroll = realWrapperHiddenRef.value.offsetWidth > realBoxWidth;
if (hasVerticalScroll) {
bufferSize = getBufferSize();
funArgs.value = initCursorIndex();
vue.nextTick(() => {
offset.value = direction.value === 'left' ? 0 : getFullWidth() - realBoxWidth;
initWidth();
listCanScroll = true;
setTimeout(() => {
animation(false, props.step);
}, props.delay);
});
} else {
init();
}
testList.value = [];
});
}
}
total = targetList.length;
});
const init = () => {
count = 0;
totalCount = 0;
listCanScroll = false;
bufferSize = 0;
funArgs.value = [];
vue.nextTick(() => {
offset.value = direction.value === 'left' ? 0 : getFullWidth() - realBoxWidth;
tempOffset = 0;
singleOffset = 0;
});
};
const getBufferSize = () => {
let tempBufferSize = targetList.length - visibleCount.value;
tempBufferSize = Math.max(1, tempBufferSize);
tempBufferSize = Math.min(5, tempBufferSize);
return tempBufferSize;
};
const add = (index, values, cb) => {
if (!!values && values.length > 0) {
if (index > targetList.length) {
index = targetList.length;
}
if (index < 0) {
index = 0;
}
const findIndexs = [];
if (values.length === 1) {
visibleItems.value.forEach((v, i) => {
if (v.index === index) {
findIndexs.push(i);
}
});
}
const datas = [];
values.forEach((v) => {
datas.push({
id: uuid(),
data: v
});
});
targetList.splice(index, 0, ...datas);
targetList.forEach((v, i) => {
v.index = i;
});
const tempBufferSize = getBufferSize();
if (listCanScroll) {
if (index < cursorIndex) {
cursorIndex += 1;
if (cursorIndex > targetList.length) {
cursorIndex = 0;
}
}
if (values.length === 1) {
findIndexs.forEach((i) => {
visibleItems.value[i] = datas[0];
});
}
if (tempBufferSize !== bufferSize) {
bufferSize = tempBufferSize;
}
} else {
const fun = () => {
const hasVerticalScroll = realWrapperHiddenRef.value.offsetWidth > realBoxWidth;
if (hasVerticalScroll) {
bufferSize = tempBufferSize;
funArgs.value = initCursorIndex();
vue.nextTick(() => {
offset.value = direction.value === 'left' ? 0 : getFullWidth() - realBoxWidth;
tempOffset = 0;
singleOffset = 0;
initWidth();
listCanScroll = true;
animation(false, props.step);
});
} else {
if (props.visibleCount === (undefined)) {
visibleCount.value = 0;
}
init();
}
testList.value = [];
};
if (visibleCount.value === 0 && props.visibleCount === (undefined)) {
initVisibleCount(() => {
fun();
});
} else {
testList.value = targetList.slice(0, visibleCount.value);
vue.nextTick(() => {
fun();
});
}
}
}
if (!!cb && typeof cb === 'function') {
cb(targetList);
}
total = targetList.length;
};
const remove = (index, num = 1, cb) => {
if (index >= 0 && index < targetList.length) {
targetList.splice(index, num);
if (listCanScroll) {
testList.value = targetList.slice(0, visibleCount.value);
vue.nextTick(() => {
const hasVerticalScroll = realWrapperHiddenRef.value.offsetWidth > realBoxWidth;
if (hasVerticalScroll) {
const tempBufferSize = getBufferSize();
if (tempBufferSize !== bufferSize) {
bufferSize = tempBufferSize;
}
funArgs.value = initCursorIndex();
} else {
init();
}
testList.value = [];
});
} else {
init();
}
}
if (!!cb && typeof cb === 'function') {
cb(targetList);
}
total = targetList.length;
};
const reset = () => {
vue.nextTick(() => {
testList.value = targetList.slice(0, visibleCount.value);
vue.nextTick(() => {
const hasVerticalScroll = realWrapperHiddenRef.value.offsetWidth > realBoxWidth;
if (hasVerticalScroll) {
if (funArgs.value[0] === 'splice') {
updateCursorIndex();
vue.nextTick(() => {
initWidth();
});
} else {
funArgs.value = initCursorIndex();
vue.nextTick(() => {
initWidth();
});
}
} else {
init();
}
});
});
};
const update = (index, value, cb) => {
if (index >= 0 && index < targetList.length) {
const findIndexs = [];
visibleItems.value.forEach((v, i) => {
if (v.index === index) {
findIndexs.push(i);
}
});
const data = {
id: uuid(),
index: index,
data: value
};
targetList[index] = data;
if (findIndexs.length > 0) {
findIndexs.forEach((i) => {
visibleItems[i] = data;
});
}
}
if (!!cb && typeof cb === 'function') {
cb(targetList);
}
};
expose({ add, remove, update, reset });
vue.watch(() => props.modelValue, (val) => {
if (val) {
animation(false, props.step);
}
});
vue.onUnmounted(() => {
cancelAnimationFrame(reqFrame);
if (!!realWrapperRef.value) {
realWrapperRef.value.parentElement.removeEventListener('mouseenter', onMouseenter);
realWrapperRef.value.parentElement.removeEventListener('mouseleave', onMouseleave);
realWrapperRef.value.parentElement.removeEventListener('wheel', onWheel);
}
});
return {
visibleItems,
offset,
testList,
realWrapperRef,
realWrapperHiddenRef
}
}
});
function render$1(_ctx, _cache, $props, $setup, $data, $options) {
return (vue.openBlock(), vue.createElementBlock(vue.Fragment, null, [
vue.createElementVNode("div", vue.mergeProps({
class: "vue3-seamless-horizontal-wrapper",
ref: "realWrapperRef",
style: { transition: `transform ${_ctx.ease}`, transform: `translateX(-${_ctx.offset}px)` }
}, _ctx.$attrs), [
(vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(_ctx.visibleItems, (item) => {
return vue.renderSlot(_ctx.$slots, "default", {
key: item.id,
data: item.data,
index: item.index,
style: "color: brown;"
})
}), 128 /* KEYED_FRAGMENT */))
], 16 /* FULL_PROPS */),
vue.createElementVNode("div", vue.mergeProps({
style: {"position":"absolute !important","top":"-999999px !important"},
ref: "realWrapperHiddenRef"
}, _ctx.$attrs), [
(vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(_ctx.testList, (item) => {
return vue.renderSlot(_ctx.$slots, "default", {
key: item.id,
data: item.data,
index: item.index
})
}), 128 /* KEYED_FRAGMENT */))
], 16 /* FULL_PROPS */)
], 64 /* STABLE_FRAGMENT */))
}
var css_248z$1 = "\n.vue3-seamless-horizontal-wrapper[data-v-530d6df5] {\n height: 100%;\n}\n";
styleInject(css_248z$1);
script$1.render = render$1;
script$1.__scopeId = "data-v-530d6df5";
script$1.__file = "packages/HorizontalScroll.vue";
var script = vue.defineComponent({
name: 'Vue3SeamlessScroll',
emits: ['offset', 'count'],
inheritAttrs: false,
props: {
// 是否开启自动滚动
modelValue: {
type: Boolean,
default: true,
},
list: {
type: Array,
required: true,
},
// 是否开启鼠标悬停
hover: {
type: Boolean,
default: false,
},
// 单步停止等待时间 (默认值 1000ms)
singleWaitTime: {
type: Number,
default: 1000,
},
// 开启鼠标悬停时支持滚轮滚动
wheel: {
type: Boolean,
default: false,
},
// 启用单行滚动
singleLine: {
type: Boolean,
default: false,
},
step: {
type: Number,
default: 0.5,
},
visibleCount: {
type: Number,
},
ease: {
type: String,
default: 'cubic-bezier(0.03, 0.76, 1, 0.16)'
},
// up,down,left,right
direction: {
type: String,
default: 'up',
},
delay: {
type: Number,
default: 0,
}
},
setup(props, { expose, emit }) {
/**
* @type {import('vue').Ref<HTMLDivElement>}
*/
const realBoxRef = vue.ref(null);
/**
* @type {import('vue').Ref<HTMLDivElement>}
*/
const realWrapperRef = vue.ref(null);
/**
* @type {import('vue').Ref<HTMLDivElement>}
*/
const realWrapperHiddenRef = vue.ref(null);
const funArgs = vue.ref([]);
const offset = vue.ref(0);
const isHover = vue.ref(false);
const direction = vue.ref(props.direction);
const isScroll = vue.computed(() => props.hover ? !isHover.value && props.modelValue : props.modelValue);
const testList = vue.ref([]);
const visibleCount = vue.ref(props.visibleCount === (undefined) ? 0 : props.visibleCount);
const targetList = listMap(props.list);
let total = 0;
let count = 0;
let totalCount = 0;
let childrenWHList = [];
let bufferTotalWH = 0;
let bufferSize = 0;
let cursorIndex = -1;
let realBoxWH = 0;
let reqFrame = null;
let singleState = false;
let singleOffset = 0;
let tempOffset = 0;
let listCanScroll = false;
const transform = vue.computed(() => {
if (direction.value === 'up' || direction.value === 'down') {
return `translateY(-${offset.value}px)`;
}
return `translateX(-${offset.value}px)`;
});
const cancle = () => {
cancelAnimationFrame(reqFrame);
reqFrame = null;
};
const updateCursorIndex = () => {
const totalIndex = cursorIndex + bufferSize;
if (totalIndex >= targetList.length) {
const tempIndex = totalIndex - targetList.length;
const tempFuns = ['splice',
[0, bufferSize],
[cursorIndex],
[0, tempIndex]];
funArgs.value = tempFuns;
if (tempIndex > targetList.length) {
cursorIndex = targetList.length;
} else {
cursorIndex = tempIndex;
}
} else {
const tempFuns = ['splice', [0, bufferSize], [cursorIndex, totalIndex]];
funArgs.value = tempFuns;
cursorIndex = totalIndex;
}
};
const initWH = () => {
const children = Array.from(realWrapperRef.value.children);
const subChildren = (direction.value === 'up' || direction.value === 'left') ?
children.slice(0, bufferSize) :
children.slice(visibleItems.value.length - bufferSize, visibleItems.value.length);
childrenWHList = subChildren.map((c) => {
if (direction.value === 'left' || direction.value === 'right') {
return c.offsetWidth;
} else {
return c.offsetHeight;
}
});
bufferTotalWH = childrenWHList
.reduce((a, b) => a + b, 0);
};
const updateOffset = () => {
if (direction.value === 'up' || direction.value === 'left') {
offset.value = 0;
} else {
offset.value = getFullWH() - realBoxWH;
}
};
const animation = (isWheel, step) => {
cancle();
if (!listCanScroll) {
return;
}
const scrollFun = () => {
const singleWH = childrenWHList[0];
let singleDone = false;
if (singleOffset >= singleWH) {
singleDone = true;
}
if (singleDone) {
childrenWHList.shift();
singleOffset = 0;
count += 1;
}
if (count === total) {
totalCount += 1;
emit('count', totalCount);
count = 0;
}
if (props.singleLine && singleDone) {
singleState = true;
setTimeout(() => {
singleState = false;
if (!isWheel) {
animation(false, props.step);
}
}, props.singleWaitTime);
} else {
if (!isWheel) {
animation(false, props.step);
}
}
};
if (isScroll.value && !singleState || isWheel) {
reqFrame = requestAnimationFrame(() => {
tempOffset += step;
singleOffset += step;
if (direction.value === 'up' || direction.value === 'left') {
offset.value += step;
} else {
offset.value -= step;
}
if (tempOffset > bufferTotalWH) {
emit('offset', bufferSize, targetList);
updateCursorIndex();
vue.nextTick(() => {
updateOffset();
tempOffset = 0;
initWH();
scrollFun();
});
} else {
scrollFun();
}
});
}
};
const onMouseenter = () => {
isHover.value = true;
};
const onMouseleave = () => {
isHover.value = false;
if (props.hover) {
animation(false, props.step);
}
};
const throttleFunc = throttle(30, (e) => {
animation(true, 10);
});
const onWheel = (e) => {
if (props.hover && props.wheel) {
throttleFunc(e);
e.preventDefault();
e.stopPropagation();
}
};
/**
* @type {import('vue').ComputedRef<Array<any>>}
*/
const visibleItems = vue.computed(() => {
let tempList = [];
if (funArgs.value.length === 0) {
tempList = targetList.slice(0, visibleCount.value);
} else if (funArgs.value[0] === 'splice') {
tempList = (direction.value === 'up' || direction.value === 'left') ? visibleItems.value : visibleItems.value.reverse();
tempList.splice(...funArgs.value[1]);
funArgs.value.slice(2).forEach(args => {
tempList.push(...targetList.slice(...args));
});
} else {
funArgs.value.slice(1).forEach(args => {
tempList.push(...targetList.slice(...args));
});
}
if (!((direction.value === 'up' || direction.value === 'left'))) {
tempList.reverse();
}
return duplicateId(tempList);
});
const initCursorIndex = () => {
cursorIndex = visibleCount.value + bufferSize;
if (cursorIndex >= targetList.length) {
const tempIndex = cursorIndex - targetList.length;
const tempFunArgs = ['slice', [0, cursorIndex], [0, tempIndex]];
if (tempIndex > targetList.length) {
cursorIndex = targetList.length;
} else {
cursorIndex = tempIndex;
}
return tempFunArgs;
} else {
return ['slice', [0, cursorIndex]];
}
};
const getFullWH = () => {
const wh = Array.from(realWrapperRef.value.children)
.map((c) => {
if (direction.value === 'left' || direction.value === 'right') {
return c.offsetWidth;
} else {
return c.offsetHeight;
}
}).reduce((a, b) => a + b, 0);
return wh;
};
const initVisibleCount = (cb) => {
if (props.visibleCount === (undefined)) {
testList.value = listMap([props.list[0]]);
vue.nextTick(() => {
if (direction.value === 'left' || direction.value === 'right') {
visibleCount.value = Math.ceil(realBoxWH / realWrapperHiddenRef.value.offsetWidth) + 2;
} else {
visibleCount.value = Math.ceil(realBoxWH / realWrapperHiddenRef.value.offsetHeight) + 2;
}
testList.value = targetList.slice(0, visibleCount.value);
vue.nextTick(() => {
cb();
});
});
} else {
testList.value = targetList.slice(0, visibleCount.value);
vue.nextTick(() => {
cb();
});
}
};
vue.onMounted(() => {
if (!!realWrapperRef.value) {
realWrapperRef.value.parentElement.addEventListener('mouseenter', onMouseenter);
realWrapperRef.value.parentElement.addEventListener('mouseleave', onMouseleave);
realWrapperRef.value.parentElement.addEventListener('wheel', onWheel);
realBoxWH = realWrapperRef.value.parentElement.offsetHeight;
if (direction.value === 'left' || direction.value === 'right') {
realBoxWH = realWrapperRef.value.parentElement.offsetWidth;
}
initVisibleCount(() => {
let hasVerticalScroll = realWrapperHiddenRef.value.offsetHeight > realBoxWH;
if (direction.value === 'left' || direction.value === 'right') {
hasVerticalScroll = realWrapperHiddenRef.value.offsetWidth > realBoxWH;
}
if (hasVerticalScroll) {
bufferSize = getBufferSize();
funArgs.value = initCursorIndex();
vue.nextTick(() => {
updateOffset();
initWH();
listCanScroll = true;
setTimeout(() => {
animation(false, props.step);
}, props.delay);
});
} else {
init();
}
testList.value = [];
});
}
total = targetList.length;
});
const init = () => {
count = 0;
totalCount = 0;
listCanScroll = false;
bufferSize = 0;
funArgs.value = [];
vue.nextTick(() => {
updateOffset();
tempOffset = 0;
singleOffset = 0;
});
};
const getBufferSize = () => {
let tempBufferSize = targetList.length - visibleCount.value;
tempBufferSize = Math.max(1, tempBufferSize);
tempBufferSize = Math.min(5, tempBufferSize);
return tempBufferSize;
};
const add = (index, values, cb) => {
if (!!values && values.length > 0) {
if (index > targetList.length) {
index = targetList.length;
}
if (index < 0) {
index = 0;
}
const findIndexs = [];
if (values.length === 1) {
visibleItems.value.forEach((v, i) => {
if (v.index === index) {
findIndexs.push(i);
}
});
}
const datas = [];
values.forEach((v) => {
datas.push({
id: uuid(),
data: v
});
});
targetList.splice(index, 0, ...datas);
targetList.forEach((v, i) => {
v.index = i;
});
const tempBufferSize = getBufferSize();
if (listCanScroll) {
if (index < cursorIndex) {
cursorIndex += 1;
if (cursorIndex > targetList.length) {
cursorIndex = 0;
}
}
if (values.length === 1 && findIndexs.length > 0) {
findIndexs.forEach((i) => {
visibleItems[i] = datas[0];
});
}
if (tempBufferSize !== bufferSize) {
bufferSize = tempBufferSize;
}
} else {
const fun = () => {
let hasVerticalScroll = realWrapperHiddenRef.value.offsetHeight > realBoxWH;
if (direction.value === 'left' || direction.value === 'right') {
hasVerticalScroll = realWrapperHiddenRef.value.offsetWidth > realBoxWH;
}
if (hasVerticalScroll) {
bufferSize = tempBufferSize;
funArgs.value = initCursorIndex();
vue.nextTick(() => {
updateOffset();
tempOffset = 0;
singleOffset = 0;
initWH();
listCanScroll = true;
animation(false, props.step);
});
} else {
if (props.visibleCount === (undefined)) {
visibleCount.value = 0;
}
init();
}
testList.value = [];
};
if (visibleCount.value === 0 && props.visibleCount === (undefined)) {
initVisibleCount(() => {
fun();
});
} else {
testList.value = targetList.slice(0, visibleCount.value);
vue.nextTick(() => {
fun();
});
}
}
}
if (!!cb && typeof cb === 'function') {
cb(targetList);
}
total = targetList.length;
};
const remove = (index, num = 1, cb) => {
if (index >= 0 && index < targetList.length) {
targetList.splice(index, num);
if (listCanScroll) {
testList.value = targetList.slice(0, visibleCount.value);
vue.nextTick(() => {
let hasVerticalScroll = realWrapperHiddenRef.value.offsetHeight > realBoxWH;
if (direction.value === 'left' || direction.value === 'right') {
hasVerticalScroll = realWrapperHiddenRef.value.offsetWidth > realBoxWH;
}
if (hasVerticalScroll) {
const tempBufferSize = getBufferSize();
if (tempBufferSize !== bufferSize) {
bufferSize = tempBufferSize;
}
funArgs.value = initCursorIndex();
} else {
init();
}
testList.value = [];
});
} else {
init();
}
}
if (!!cb && typeof cb === 'function') {
cb(targetList);
}
total = targetList.length;
};
const reset = () => {
vue.nextTick(() => {
testList.value = targetList.slice(0, visibleCount.value);
vue.nextTick(() => {
let hasVerticalScroll = realWrapperHiddenRef.value.offsetHeight > realBoxWH;
if (direction.value === 'left' || direction.value === 'right') {
hasVerticalScroll = realWrapperHiddenRef.value.offsetWidth > realBoxWH;
}
if (hasVerticalScroll) {
if (funArgs.value[0] === 'splice') {
updateCursorIndex();
vue.nextTick(() => {
initWH();
});
} else {
funArgs.value = initCursorIndex();
vue.nextTick(() => {
initWH();
});
}
} else {
init();
}
});
});
};
const update = (index, value, cb) => {
if (index >= 0 && index < targetList.length) {
const findIndexs = [];
visibleItems.value.forEach((v, i) => {
if (v.index === index) {
findIndexs.push(i);
}
});
const data = {
id: uuid(),
index: index,
data: value
};
targetList[index] = data;
if (findIndexs.length > 0) {
findIndexs.forEach((i) => {
visibleItems[i] = data;
});
}
}
if (!!cb && typeof cb === 'function') {
cb(targetList);
}
};
expose({ add, remove, update, reset });
vue.watch(() => props.modelValue, (val) => {
if (val) {
animation(false, props.step);
}
});
vue.onUnmounted(() => {
cancelAnimationFrame(reqFrame);
if (!!realWrapperRef.value) {
realWrapperRef.value.parentElement.removeEventListener('mouseenter', onMouseenter);
realWrapperRef.value.parentElement.removeEventListener('mouseleave', onMouseleave);
realWrapperRef.value.parentElement.removeEventListener('wheel', onWheel);
}
});
return {
realBoxRef,
realWrapperRef,
visibleItems,
offset,
testList,
realWrapperHiddenRef,
transform,
}
}
});
function render(_ctx, _cache, $props, $setup, $data, $options) {
return (vue.openBlock(), vue.createElementBlock(vue.Fragment, null, [
vue.createElementVNode("div", vue.mergeProps({
class: "vue3-seamless-wrapper",
ref: "realWrapperRef",
style: { transition: `transform ${_ctx.ease}`, transform: _ctx.transform }
}, _ctx.$attrs), [
(vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(_ctx.visibleItems, (item) => {
return vue.renderSlot(_ctx.$slots, "default", {
key: item.id,
data: item.data,
index: item.index
})
}), 128 /* KEYED_FRAGMENT */))
], 16 /* FULL_PROPS */),
vue.createElementVNode("div", vue.mergeProps({
style: {"position":"absolute !important","left":"-999999px !important"},
ref: "realWrapperHiddenRef"
}, _ctx.$attrs), [
(vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(_ctx.testList, (item) => {
return vue.renderSlot(_ctx.$slots, "default", {
key: item.id,
data: item.data,
index: item.index
})
}), 128 /* KEYED_FRAGMENT */))
], 16 /* FULL_PROPS */)
], 64 /* STABLE_FRAGMENT */))
}
var css_248z = "\n.vue3-seamless-wrapper[data-v-b156a416] {\r\n width: 100%;\r\n height: 100%;\n}\r\n";
styleInject(css_248z);
script.render = render;
script.__scopeId = "data-v-b156a416";
script.__file = "packages/Vue3SeamlessScroll.vue";
const install = function (app, options = {}) {
app.component(options.name || script.name, script);
};
function index (app) {
app.use(install);
}
exports.HorizontalScroll = script$1;
exports.VerticalScroll = script$2;
exports.Vue3SeamlessScroll = script;
exports.default = index;
Object.defineProperty(exports, '__esModule', { value: true });
}));