vue-roller
Version:
Fluid and smooth rolling animation for Vue.js
278 lines (277 loc) • 10.6 kB
JavaScript
import { ref, computed, watch, onMounted, nextTick, defineComponent, toRefs, openBlock, createElementBlock, normalizeStyle, unref, normalizeClass, createElementVNode, Fragment, renderList, toDisplayString, createBlock, TransitionGroup, withCtx } from "vue";
function useReloadAnimation(duration) {
const isReady = ref(false);
const isEnd = ref(false);
let outerTimer;
let innerTimer;
let animationEnd = function() {
};
return {
reloadAnimation: () => {
isReady.value = false;
isEnd.value = false;
clearTimeout(outerTimer);
clearTimeout(innerTimer);
outerTimer = setTimeout(() => {
isReady.value = true;
innerTimer = setTimeout(() => {
isEnd.value = true;
animationEnd();
}, duration.value);
}, 100);
},
isReady,
isEnd,
onAnimationEnd: (fn) => {
animationEnd = fn;
}
};
}
function useAnimationManager(char, defaultChar, charSet, duration) {
const targetIdx = computed(() => charSet.value.indexOf(char.value));
const prevTargetIdx = ref(charSet.value.indexOf(defaultChar.value));
const { reloadAnimation, isReady, isEnd } = useReloadAnimation(duration);
watch(char, (next, prev) => {
prevTargetIdx.value = charSet.value.indexOf(prev);
reloadAnimation();
});
reloadAnimation();
return { isReady, isEnd, targetIdx, prevTargetIdx };
}
function useMeasureText(itemElement) {
const width = ref(0);
function updateWidth() {
var _a;
width.value = ((_a = itemElement.value) == null ? void 0 : _a.clientWidth) || 16;
}
onMounted(updateWidth);
watch(itemElement, updateWidth);
return { width };
}
function useSelectElement(itemElements, targetIdx) {
const itemElement = ref(null);
function updateItemElement() {
itemElement.value = itemElements.value[targetIdx.value];
}
onMounted(updateItemElement);
watch(targetIdx, () => {
nextTick(updateItemElement);
});
return { itemElement };
}
var RollerItemMode = /* @__PURE__ */ ((RollerItemMode2) => {
RollerItemMode2["SHORT"] = "short";
RollerItemMode2["LONG"] = "long";
return RollerItemMode2;
})(RollerItemMode || {});
var RollerItemCharSet = /* @__PURE__ */ ((RollerItemCharSet2) => {
RollerItemCharSet2["NUMBER"] = "number";
RollerItemCharSet2["ALPHABET"] = "alphabet";
return RollerItemCharSet2;
})(RollerItemCharSet || {});
const RollerCharSet = {
["number"]: [" ", ...[...Array(10).keys()].map(String), ","],
["alphabet"]: [
" ",
"a",
"b",
"c",
"d",
"e",
"f",
"g",
"h",
"i",
"j",
"k",
"l",
"m",
"n",
"o",
"p",
"q",
"r",
"s",
"t",
"u",
"v",
"w",
"x",
"y",
"z",
"A",
"B",
"C",
"D",
"E",
"F",
"G",
"H",
"I",
"J",
"K",
"L",
"M",
"N",
"O",
"P",
"Q",
"R",
"S",
"T",
"U",
"V",
"W",
"X",
"Y",
"Z",
"-"
]
};
var RollerItem_vue_vue_type_style_index_0_scoped_true_lang = /* @__PURE__ */ (() => ".roller-item[data-v-5357a046]{position:relative;height:1em;transition:width .5s}.roller-item .roller-item__wrapper[data-v-5357a046]{position:relative;display:flex;align-items:center;width:100%;height:200%;overflow:hidden;top:-50%;mask-image:linear-gradient(0deg,rgba(255,255,255,0) 0%,rgb(0,0,0) 25%,rgb(0,0,0) 75%,rgba(255,255,255,0) 100%);-webkit-mask-image:linear-gradient(0deg,rgba(255,255,255,0) 0%,rgb(0,0,0) 25%,rgb(0,0,0) 75%,rgba(255,255,255,0) 100%);box-sizing:border-box}.roller-item .roller-item__wrapper.roller-item__wrapper--short[data-v-5357a046]{mask-image:linear-gradient(0deg,rgba(255,255,255,0) 22%,rgb(0,0,0) 30%,rgb(0,0,0) 70%,rgba(255,255,255,0) 78%);-webkit-mask-image:linear-gradient(0deg,rgba(255,255,255,0) 22%,rgb(0,0,0) 30%,rgb(0,0,0) 70%,rgba(255,255,255,0) 78%)}.roller-item .roller-item__wrapper .roller-item__wrapper__list[data-v-5357a046]{position:absolute;width:100%;display:flex;align-items:center;flex-direction:column;box-sizing:border-box;transition:.25s}.roller-item .roller-item__wrapper .roller-item__wrapper__list .roller-item__wrapper__list__item[data-v-5357a046]{display:flex;width:fit-content;height:1em;line-height:1;box-sizing:border-box;user-select:none}.roller-item .roller-item__wrapper .roller-item__wrapper__list .roller-item__wrapper__list__item--target[data-v-5357a046]{user-select:text}\n")();
var _export_sfc = (sfc, props) => {
const target = sfc.__vccOpts || sfc;
for (const [key, val] of props) {
target[key] = val;
}
return target;
};
const _hoisted_1 = { class: "roller-item__wrapper__list" };
const _sfc_main$1 = defineComponent({
name: "RollerItem",
props: {
char: { default: "" },
defaultChar: { default: "" },
duration: { default: 500 },
charSet: { default: () => RollerCharSet[RollerItemCharSet.NUMBER] },
mode: { default: RollerItemMode.SHORT }
},
setup(__props) {
const props = __props;
const { char, defaultChar, charSet, duration } = toRefs(props);
const { isReady, isEnd, targetIdx, prevTargetIdx } = useAnimationManager(char, defaultChar, charSet, duration);
const itemElements = ref([]);
const { itemElement } = useSelectElement(itemElements, targetIdx);
const { width } = useMeasureText(itemElement);
const top = computed(() => {
if (!isReady.value) {
if (prevTargetIdx.value !== -1)
return `-${prevTargetIdx.value / charSet.value.length * 100}%`;
return "0%";
}
if (targetIdx.value === -1)
return `-${1 / charSet.value.length * 100}%`;
return `-${targetIdx.value / charSet.value.length * 100}%`;
});
const shortCharSet = computed(() => {
if (props.mode == RollerItemMode.SHORT)
return [props.char];
if (targetIdx.value == -1)
return ["", props.char, ""];
if (targetIdx.value == 0)
return ["", props.char, props.charSet[targetIdx.value + 1]];
if (targetIdx.value == charSet.value.length - 1)
return [props.charSet[targetIdx.value - 1], props.char, ""];
return props.charSet.slice(targetIdx.value - 1, targetIdx.value + 2);
});
return (_ctx, _cache) => {
return openBlock(), createElementBlock("div", {
class: "roller-item",
style: normalizeStyle({ width: `${unref(width)}px` })
}, [
unref(isEnd) ? (openBlock(), createElementBlock("div", {
key: 0,
class: normalizeClass(["roller-item__wrapper", { "roller-item__wrapper--short": __props.mode == unref(RollerItemMode).SHORT }])
}, [
createElementVNode("div", _hoisted_1, [
(openBlock(true), createElementBlock(Fragment, null, renderList(unref(shortCharSet), (item) => {
return openBlock(), createElementBlock("div", {
class: normalizeClass(["roller-item__wrapper__list__item", { "roller-item__wrapper__list__item--target": item == unref(char) }]),
ref_for: true,
ref_key: "itemElements",
ref: itemElements
}, toDisplayString(item), 3);
}), 256))
])
], 2)) : (openBlock(), createElementBlock("div", {
key: 1,
class: normalizeClass(["roller-item__wrapper", { "roller-item__wrapper--short": __props.mode == unref(RollerItemMode).SHORT }])
}, [
createElementVNode("div", {
class: "roller-item__wrapper__list",
style: normalizeStyle({ top: "25%", transform: `translateY(${unref(top)})`, transition: `transform ${unref(duration)}ms` })
}, [
(openBlock(true), createElementBlock(Fragment, null, renderList(unref(charSet), (item) => {
return openBlock(), createElementBlock("div", {
class: normalizeClass(["roller-item__wrapper__list__item", { "roller-item__wrapper__list__item--target": item == unref(char) }]),
ref_for: true,
ref_key: "itemElements",
ref: itemElements
}, toDisplayString(item), 3);
}), 256))
], 4)
], 2))
], 4);
};
}
});
var RollerItem = /* @__PURE__ */ _export_sfc(_sfc_main$1, [["__scopeId", "data-v-5357a046"]]);
var Roller_vue_vue_type_style_index_0_scoped_true_lang = /* @__PURE__ */ (() => ".roller[data-v-4760d5f9]{display:flex;flex-wrap:wrap}.roller-list-enter-active[data-v-4760d5f9],.roller-list-leave-active[data-v-4760d5f9]{transition:.5s}.roller-list-enter-from[data-v-4760d5f9]{opacity:0}.roller-list-enter-to[data-v-4760d5f9],.roller-list-leave-from[data-v-4760d5f9]{opacity:1}.roller-list-leave-to[data-v-4760d5f9]{opacity:0;width:0px!important}\n")();
const _sfc_main = defineComponent({
name: "Roller",
props: {
value: { default: "123" },
defaultValue: { default: "" },
duration: { default: 500 },
charSet: null,
mode: null
},
emits: ["animation-end"],
setup(__props, { emit }) {
const props = __props;
const { duration, value } = toRefs(props);
const { reloadAnimation, onAnimationEnd } = useReloadAnimation(duration);
watch([value], () => {
reloadAnimation();
});
onAnimationEnd(() => emit("animation-end"));
reloadAnimation();
const charArray = computed(() => [...props.value]);
const defaultCharArray = computed(() => [...props.defaultValue]);
const computedCharSet = computed(() => {
if (Array.isArray(props.charSet))
return props.charSet;
return RollerCharSet[props.charSet];
});
return (_ctx, _cache) => {
return openBlock(), createBlock(TransitionGroup, {
tag: "div",
name: "roller-list",
class: "roller"
}, {
default: withCtx(() => [
(openBlock(true), createElementBlock(Fragment, null, renderList(unref(charArray), (char, idx) => {
return openBlock(), createBlock(RollerItem, {
char,
duration: unref(duration),
charSet: unref(computedCharSet),
defaultChar: unref(defaultCharArray)[idx],
mode: __props.mode,
key: idx
}, null, 8, ["char", "duration", "charSet", "defaultChar", "mode"]);
}), 128))
]),
_: 1
});
};
}
});
var Roller = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-4760d5f9"]]);
const install = (Vue) => {
Vue.component(Roller.name, Roller);
Vue.component(RollerItem.name, RollerItem);
};
if (typeof window !== "undefined" && window.Vue) {
install(window.Vue);
}
export { Roller, RollerItem, install };