mirror of
https://github.com/DavidHDev/vue-bits.git
synced 2026-03-07 14:39:30 -07:00
Add copy button styles and fix CurvedLoop for Safari
This commit is contained in:
@@ -25,10 +25,10 @@ const text = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const measureRef = ref<SVGTextElement | null>(null);
|
const measureRef = ref<SVGTextElement | null>(null);
|
||||||
const tspansRef = ref<SVGTSpanElement[]>([]);
|
const textPathRef = ref<SVGTextPathElement | null>(null);
|
||||||
const pathRef = ref<SVGPathElement | null>(null);
|
const pathRef = ref<SVGPathElement | null>(null);
|
||||||
const pathLength = ref(0);
|
|
||||||
const spacing = ref(0);
|
const spacing = ref(0);
|
||||||
|
const offset = ref(0);
|
||||||
const uid = Math.random().toString(36).substr(2, 9);
|
const uid = Math.random().toString(36).substr(2, 9);
|
||||||
const pathId = `curve-${uid}`;
|
const pathId = `curve-${uid}`;
|
||||||
|
|
||||||
@@ -41,34 +41,38 @@ const velRef = ref(0);
|
|||||||
|
|
||||||
let animationFrame: number | null = null;
|
let animationFrame: number | null = null;
|
||||||
|
|
||||||
|
const textLength = computed(() => spacing.value);
|
||||||
|
const totalText = computed(() => {
|
||||||
|
return textLength.value
|
||||||
|
? Array(Math.ceil(1800 / textLength.value) + 2)
|
||||||
|
.fill(text.value)
|
||||||
|
.join('')
|
||||||
|
: text.value;
|
||||||
|
});
|
||||||
|
const ready = computed(() => spacing.value > 0);
|
||||||
|
|
||||||
const updateSpacing = () => {
|
const updateSpacing = () => {
|
||||||
if (measureRef.value) {
|
if (measureRef.value) {
|
||||||
spacing.value = measureRef.value.getComputedTextLength();
|
spacing.value = measureRef.value.getComputedTextLength();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const updatePathLength = () => {
|
|
||||||
if (pathRef.value) {
|
|
||||||
pathLength.value = pathRef.value.getTotalLength();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const animate = () => {
|
const animate = () => {
|
||||||
if (!spacing.value) return;
|
if (!spacing.value || !ready.value) return;
|
||||||
|
|
||||||
const step = () => {
|
const step = () => {
|
||||||
tspansRef.value.forEach(t => {
|
if (!dragRef.value && textPathRef.value) {
|
||||||
if (!t) return;
|
const delta = dirRef.value === 'right' ? props.speed : -props.speed;
|
||||||
let x = parseFloat(t.getAttribute('x') || '0');
|
const currentOffset = parseFloat(textPathRef.value.getAttribute('startOffset') || '0');
|
||||||
if (!dragRef.value) {
|
let newOffset = currentOffset + delta;
|
||||||
const delta = dirRef.value === 'right' ? Math.abs(props.speed) : -Math.abs(props.speed);
|
|
||||||
x += delta;
|
const wrapPoint = spacing.value;
|
||||||
|
if (newOffset <= -wrapPoint) newOffset += wrapPoint;
|
||||||
|
if (newOffset >= wrapPoint) newOffset -= wrapPoint;
|
||||||
|
|
||||||
|
textPathRef.value.setAttribute('startOffset', newOffset + 'px');
|
||||||
|
offset.value = newOffset;
|
||||||
}
|
}
|
||||||
const maxX = (tspansRef.value.length - 1) * spacing.value;
|
|
||||||
if (x < -spacing.value) x = maxX;
|
|
||||||
if (x > maxX) x = -spacing.value;
|
|
||||||
t.setAttribute('x', x.toString());
|
|
||||||
});
|
|
||||||
animationFrame = requestAnimationFrame(step);
|
animationFrame = requestAnimationFrame(step);
|
||||||
};
|
};
|
||||||
step();
|
step();
|
||||||
@@ -81,12 +85,6 @@ const stopAnimation = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const repeats = computed(() => {
|
|
||||||
return pathLength.value && spacing.value ? Math.ceil(pathLength.value / spacing.value) + 2 : 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
const ready = computed(() => pathLength.value > 0 && spacing.value > 0);
|
|
||||||
|
|
||||||
const onPointerDown = (e: PointerEvent) => {
|
const onPointerDown = (e: PointerEvent) => {
|
||||||
if (!props.interactive) return;
|
if (!props.interactive) return;
|
||||||
dragRef.value = true;
|
dragRef.value = true;
|
||||||
@@ -96,19 +94,20 @@ const onPointerDown = (e: PointerEvent) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onPointerMove = (e: PointerEvent) => {
|
const onPointerMove = (e: PointerEvent) => {
|
||||||
if (!props.interactive || !dragRef.value) return;
|
if (!props.interactive || !dragRef.value || !textPathRef.value) return;
|
||||||
const dx = e.clientX - lastXRef.value;
|
const dx = e.clientX - lastXRef.value;
|
||||||
lastXRef.value = e.clientX;
|
lastXRef.value = e.clientX;
|
||||||
velRef.value = dx;
|
velRef.value = dx;
|
||||||
tspansRef.value.forEach(t => {
|
|
||||||
if (!t) return;
|
const currentOffset = parseFloat(textPathRef.value.getAttribute('startOffset') || '0');
|
||||||
let x = parseFloat(t.getAttribute('x') || '0');
|
let newOffset = currentOffset + dx;
|
||||||
x += dx;
|
|
||||||
const maxX = (tspansRef.value.length - 1) * spacing.value;
|
const wrapPoint = spacing.value;
|
||||||
if (x < -spacing.value) x = maxX;
|
if (newOffset <= -wrapPoint) newOffset += wrapPoint;
|
||||||
if (x > maxX) x = -spacing.value;
|
if (newOffset >= wrapPoint) newOffset -= wrapPoint;
|
||||||
t.setAttribute('x', x.toString());
|
|
||||||
});
|
textPathRef.value.setAttribute('startOffset', newOffset + 'px');
|
||||||
|
offset.value = newOffset;
|
||||||
};
|
};
|
||||||
|
|
||||||
const endDrag = () => {
|
const endDrag = () => {
|
||||||
@@ -124,7 +123,6 @@ const cursorStyle = computed(() => {
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
updateSpacing();
|
updateSpacing();
|
||||||
updatePathLength();
|
|
||||||
animate();
|
animate();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -139,25 +137,12 @@ watch([text, () => props.className], () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.curveAmount,
|
|
||||||
() => {
|
|
||||||
nextTick(() => {
|
|
||||||
updatePathLength();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
watch([spacing, () => props.speed], () => {
|
watch([spacing, () => props.speed], () => {
|
||||||
stopAnimation();
|
stopAnimation();
|
||||||
if (spacing.value) {
|
if (spacing.value) {
|
||||||
animate();
|
animate();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(repeats, () => {
|
|
||||||
tspansRef.value = [];
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -173,7 +158,7 @@ watch(repeats, () => {
|
|||||||
@pointerleave="endDrag"
|
@pointerleave="endDrag"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
class="select-none w-full overflow-visible block aspect-[100/12] text-[6rem] font-bold tracking-[5px] uppercase leading-none"
|
class="select-none w-full overflow-visible block aspect-[100/12] text-[6rem] font-bold uppercase leading-none"
|
||||||
viewBox="0 0 1440 120"
|
viewBox="0 0 1440 120"
|
||||||
>
|
>
|
||||||
<text ref="measureRef" xml:space="preserve" style="visibility: hidden; opacity: 0; pointer-events: none">
|
<text ref="measureRef" xml:space="preserve" style="visibility: hidden; opacity: 0; pointer-events: none">
|
||||||
@@ -185,19 +170,8 @@ watch(repeats, () => {
|
|||||||
</defs>
|
</defs>
|
||||||
|
|
||||||
<text v-if="ready" xml:space="preserve" :class="`fill-white ${className}`">
|
<text v-if="ready" xml:space="preserve" :class="`fill-white ${className}`">
|
||||||
<textPath :href="`#${pathId}`" xml:space="preserve">
|
<textPath ref="textPathRef" :href="`#${pathId}`" :startOffset="offset + 'px'" xml:space="preserve">
|
||||||
<tspan
|
{{ totalText }}
|
||||||
v-for="i in repeats"
|
|
||||||
:key="i"
|
|
||||||
:x="(i - 1) * spacing"
|
|
||||||
:ref="
|
|
||||||
el => {
|
|
||||||
if (el) tspansRef[i - 1] = el as SVGTSpanElement;
|
|
||||||
}
|
|
||||||
"
|
|
||||||
>
|
|
||||||
{{ text }}
|
|
||||||
</tspan>
|
|
||||||
</textPath>
|
</textPath>
|
||||||
</text>
|
</text>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
@@ -52,14 +52,14 @@ body {
|
|||||||
.v-code-block--code-copy-button {
|
.v-code-block--code-copy-button {
|
||||||
border: 1px solid #333;
|
border: 1px solid #333;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: .8em !important;
|
padding: 0.8em !important;
|
||||||
height: 35px !important;
|
height: 35px !important;
|
||||||
top: .5em !important;
|
top: 0.5em !important;
|
||||||
right: .5em !important;
|
right: 0.5em !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-code-block--code-copy-button.v-code-block--code-copy-button-status-success svg {
|
.v-code-block--code-copy-button.v-code-block--code-copy-button-status-success svg {
|
||||||
fill: #27FF64 !important;
|
fill: #27ff64 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-code-block--code-copy-button svg {
|
.v-code-block--code-copy-button svg {
|
||||||
|
|||||||
Reference in New Issue
Block a user