fix(mini-user): 门店核销走马灯改用 JS 位移

微信端 CSS max-content/百分比 transform 不可靠;改为测量宽度后 translateX 滚动,并上移到店名下方。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-03 14:33:31 +08:00
parent fd05621660
commit 021b9e0329
4 changed files with 142 additions and 45 deletions
@@ -0,0 +1,84 @@
import { useEffect, useMemo, useRef, useState } from 'react';
import { View, Text } from '@tarojs/components';
import Taro from '@tarojs/taro';
type StoreRedeemMarqueeProps = {
lines: string[];
};
/**
* 横向走马灯(JS 位移)。
* 微信小程序对 CSS max-content / translateX(-50%) 宽度计算不稳定,故不用纯 CSS 动画。
*/
export default function StoreRedeemMarquee({ lines }: StoreRedeemMarqueeProps) {
const text = useMemo(
() =>
lines
.map((s) => String(s || '').trim())
.filter(Boolean)
.join('  '),
[lines],
);
const [offset, setOffset] = useState(0);
const halfWidthRef = useRef(0);
const measureIdRef = useRef(`m${Date.now().toString(36)}`);
useEffect(() => {
setOffset(0);
halfWidthRef.current = 0;
if (!text) return;
const measure = () => {
Taro.createSelectorQuery()
.select(`#${measureIdRef.current}`)
.boundingClientRect()
.exec((res) => {
const width = Number(res?.[0]?.width || 0);
if (width > 0) halfWidthRef.current = width;
});
};
measure();
const t1 = setTimeout(measure, 80);
const t2 = setTimeout(measure, 320);
return () => {
clearTimeout(t1);
clearTimeout(t2);
};
}, [text]);
useEffect(() => {
if (!text) return;
const speedPxPerSec = 48;
const intervalMs = 32;
const step = (speedPxPerSec * intervalMs) / 1000;
const timer = setInterval(() => {
setOffset((prev) => {
const half = halfWidthRef.current || Math.max(text.length * 7, 120);
const next = prev - step;
return -next >= half ? 0 : next;
});
}, intervalMs);
return () => clearInterval(timer);
}, [text]);
if (!text) return null;
const segment = `${text}  `;
return (
<View className="store-detail-marquee">
<View
className="store-detail-marquee-track"
style={{ transform: `translateX(${offset}px)` }}
>
<View id={measureIdRef.current} className="store-detail-marquee-item">
<Text className="store-detail-marquee-item-text">{segment}</Text>
</View>
<View className="store-detail-marquee-item">
<Text className="store-detail-marquee-item-text">{segment}</Text>
</View>
</View>
</View>
);
}