Reduce performance lost on large screens
Some checks failed
Deploy with Docker Compose / deploy (push) Has been cancelled
Some checks failed
Deploy with Docker Compose / deploy (push) Has been cancelled
This commit is contained in:
@@ -5,21 +5,31 @@ const container = useTemplateRef("container");
|
|||||||
const item1 = useTemplateRef("item1");
|
const item1 = useTemplateRef("item1");
|
||||||
|
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
|
let cachedWidth = 0;
|
||||||
|
|
||||||
let rafId;
|
let rafId;
|
||||||
|
|
||||||
const speed = 0.5; // pixels per frame
|
const speed = 0.5; // pixels per frame
|
||||||
|
|
||||||
function animate() {
|
function measureWidth() {
|
||||||
const ctnr = container.value;
|
const ctnr = container.value;
|
||||||
const it1 = item1.value;
|
const it1 = item1.value;
|
||||||
|
if (ctnr && it1) {
|
||||||
|
cachedWidth = Math.max(ctnr.offsetWidth, it1.scrollWidth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const width = Math.max(ctnr.offsetWidth, it1.scrollWidth);
|
function animate() {
|
||||||
|
const ctnr = container.value;
|
||||||
|
if (!ctnr || cachedWidth === 0) {
|
||||||
|
rafId = requestAnimationFrame(animate);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
offset -= speed;
|
offset -= speed;
|
||||||
|
|
||||||
if (offset <= -width) {
|
if (offset <= -cachedWidth) {
|
||||||
offset += width;
|
offset += cachedWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctnr.style.transform = `translateX(${offset}px)`;
|
ctnr.style.transform = `translateX(${offset}px)`;
|
||||||
@@ -27,12 +37,19 @@ function animate() {
|
|||||||
rafId = requestAnimationFrame(animate);
|
rafId = requestAnimationFrame(animate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let resizeObserver;
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
measureWidth();
|
||||||
rafId = requestAnimationFrame(animate);
|
rafId = requestAnimationFrame(animate);
|
||||||
|
|
||||||
|
resizeObserver = new ResizeObserver(measureWidth);
|
||||||
|
resizeObserver.observe(container.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
cancelAnimationFrame(rafId);
|
cancelAnimationFrame(rafId);
|
||||||
|
resizeObserver?.disconnect();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,12 @@ let pos = 0;
|
|||||||
let direction = 1; // 1 = down, -1 = up
|
let direction = 1; // 1 = down, -1 = up
|
||||||
let timeoutId;
|
let timeoutId;
|
||||||
let timeoutId2;
|
let timeoutId2;
|
||||||
|
let cachedScrollHeight = 0;
|
||||||
|
|
||||||
|
function measureScrollHeight() {
|
||||||
|
const el = container.value;
|
||||||
|
if (el) cachedScrollHeight = el.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
function handleHover() {
|
function handleHover() {
|
||||||
cancelAnimationFrame(timeoutId);
|
cancelAnimationFrame(timeoutId);
|
||||||
@@ -28,6 +34,10 @@ function handleHover() {
|
|||||||
|
|
||||||
function tick() {
|
function tick() {
|
||||||
const el = container.value;
|
const el = container.value;
|
||||||
|
if (!el || cachedScrollHeight === 0) {
|
||||||
|
timeoutId = requestAnimationFrame(tick);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const reachedBottom = pos <= 0;
|
const reachedBottom = pos <= 0;
|
||||||
const reachedTop = pos >= 1;
|
const reachedTop = pos >= 1;
|
||||||
@@ -46,16 +56,23 @@ function tick() {
|
|||||||
|
|
||||||
pos += direction * SPEED;
|
pos += direction * SPEED;
|
||||||
|
|
||||||
el.scrollTop = pos * el.scrollHeight;
|
el.scrollTop = pos * cachedScrollHeight;
|
||||||
|
|
||||||
timeoutId = requestAnimationFrame(tick);
|
timeoutId = requestAnimationFrame(tick);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let resizeObserver;
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
measureScrollHeight();
|
||||||
timeoutId = requestAnimationFrame(tick);
|
timeoutId = requestAnimationFrame(tick);
|
||||||
|
|
||||||
|
resizeObserver = new ResizeObserver(measureScrollHeight);
|
||||||
|
resizeObserver.observe(container.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
cancelAnimationFrame(timeoutId);
|
cancelAnimationFrame(timeoutId);
|
||||||
|
resizeObserver?.disconnect();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -23,49 +23,83 @@ const phrases = [
|
|||||||
"I like anime, all kinds of music and sci fic",
|
"I like anime, all kinds of music and sci fic",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Non-reactive animation state to avoid triggering Vue re-renders every frame
|
||||||
|
const animState = phrases.map((text, i) => ({
|
||||||
|
x: i * 20,
|
||||||
|
y: i * 20,
|
||||||
|
dx: rand(0, 30) / 100,
|
||||||
|
dy: 0.5,
|
||||||
|
content: text,
|
||||||
|
cachedW: 0,
|
||||||
|
cachedH: 0,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Reactive items only for initial render
|
||||||
const items = ref<Item[]>(
|
const items = ref<Item[]>(
|
||||||
phrases.map((text, i) => ({
|
animState.map((s) => ({
|
||||||
x: i * 20,
|
x: s.x,
|
||||||
y: i * 20,
|
y: s.y,
|
||||||
dx: rand(0, 30) / 100,
|
dx: s.dx,
|
||||||
dy: 0.5,
|
dy: s.dy,
|
||||||
content: text,
|
content: s.content,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
|
||||||
let rafId = 0;
|
let rafId = 0;
|
||||||
|
let cachedCW = 0;
|
||||||
|
let cachedCH = 0;
|
||||||
|
|
||||||
|
function measureSizes() {
|
||||||
|
const c = container.value;
|
||||||
|
if (c) {
|
||||||
|
cachedCW = c.clientWidth;
|
||||||
|
cachedCH = c.clientHeight;
|
||||||
|
}
|
||||||
|
itemEls.value.forEach((el, i) => {
|
||||||
|
if (el && animState[i]) {
|
||||||
|
animState[i].cachedW = el.offsetWidth;
|
||||||
|
animState[i].cachedH = el.offsetHeight;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function animate() {
|
function animate() {
|
||||||
const c = container.value;
|
if (!cachedCW || !cachedCH) {
|
||||||
if (!c) return;
|
rafId = requestAnimationFrame(animate);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const cw = c.clientWidth;
|
for (let i = 0; i < animState.length; i++) {
|
||||||
const ch = c.clientHeight;
|
const s = animState[i];
|
||||||
|
|
||||||
items.value.forEach((item, i) => {
|
|
||||||
const el = itemEls.value[i];
|
const el = itemEls.value[i];
|
||||||
if (!el) return;
|
if (!el) continue;
|
||||||
|
|
||||||
const ew = el.offsetWidth;
|
s.x += s.dx;
|
||||||
const eh = el.offsetHeight;
|
s.y += s.dy;
|
||||||
|
|
||||||
item.x += item.dx;
|
if (s.x < 0 || s.x > cachedCW - s.cachedW) s.dx *= -1;
|
||||||
item.y += item.dy;
|
if (s.y < 0 || s.y > cachedCH - s.cachedH) s.dy *= -1;
|
||||||
|
|
||||||
if (item.x < 0 || item.x > cw - ew) item.dx *= -1;
|
el.style.transform = `translate(${s.x}px, ${s.y}px)`;
|
||||||
if (item.y < 0 || item.y > ch - eh) item.dy *= -1;
|
}
|
||||||
});
|
|
||||||
|
|
||||||
rafId = requestAnimationFrame(animate);
|
rafId = requestAnimationFrame(animate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let resizeObserver: ResizeObserver;
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await nextTick();
|
await nextTick();
|
||||||
|
measureSizes();
|
||||||
rafId = requestAnimationFrame(animate);
|
rafId = requestAnimationFrame(animate);
|
||||||
|
|
||||||
|
resizeObserver = new ResizeObserver(measureSizes);
|
||||||
|
resizeObserver.observe(container.value!);
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
cancelAnimationFrame(rafId);
|
cancelAnimationFrame(rafId);
|
||||||
|
resizeObserver?.disconnect();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -79,9 +113,6 @@ onUnmounted(() => {
|
|||||||
:key="i"
|
:key="i"
|
||||||
ref="itemEls"
|
ref="itemEls"
|
||||||
class="absolute w-fit h-fit"
|
class="absolute w-fit h-fit"
|
||||||
:style="{
|
|
||||||
transform: `translate(${item.x}px, ${item.y}px)`,
|
|
||||||
}"
|
|
||||||
>
|
>
|
||||||
<h1>
|
<h1>
|
||||||
{{ item.content }}
|
{{ item.content }}
|
||||||
|
|||||||
Reference in New Issue
Block a user