如何在只知道它的索引的情况下获得段落内单词的边界矩形?

我正在一个 React 应用程序中创建一个文本转语音功能,该功能可以通过在其后面放置背景来突出显示当前所说的单词。

该功能与Firefox 阅读器视图非常相似。

我实现的解决方案只是剪切段落字符串,并在每次渲染时在口语单词周围放置一个跨度,这使得资源占用很大并且无法制作动画。

这是代码:(我打算废弃)

export interface SpeakEvent {

    start: number;

    end: number;

    type: string;

}


export default function TextNode({ content }: TextNodeProps) {

    const [highlight, setHighlight] = useState<SpeakEvent | null>(null);


    useEffect(() => {

        registerText((ev) => {

            if (ev?.type === 'word' || !ev)

                setHighlight((old) => {

                    /* Irrelevant code */

                    return ev;

                });

        }, content);

    }, [content]);


    const { start, end } = highlight ?? {};


    let segments = [content];


    if (highlight) {

        segments = [

            segments[0].slice(0, start),

            segments[0].slice(start, end),

            segments[0].slice(end),

        ];

    }


    return (

        <>

            {segments.map((seg, i) =>

                i === 1 ? (

                    <span key={i} className={'highlight'}>

                        {seg}

                    </span>

                ) : (

                    seg

                )

            )}

        </>

    );

}

Firefox 阅读器使用更智能的方式来做到这一点。它使用放置在口语单词后面的 div,然后将其四处移动

https://img1.sycdn.imooc.com/659f880c0001746612200184.jpg

包含高亮效果的div直接使用绝对坐标放置。

他们如何在只知道字符串索引的情况下访问段落内单词的边界矩形?



繁星淼淼
浏览 37回答 1
1回答

慕莱坞森

这是以下解决方案的结果编辑2:正如评论中提到的,当屏幕改变尺寸以及用户缩放或滚动时,固定定位会导致问题。要创建相对定位,可以首先获取父元素的偏移量:const { offsetTop, offsetLeft } = containerEl.current;然后将它们减去以获取 DomRect :return Array.from(range.getClientRects()).map(    ({ top, left, width, height }) => ({        top: top - offsetTop,        left: left - offsetLeft,        width,        height,    }));只需应用于position: relative文本父级,然后position: absolute应用于文本叠加即可。编辑:下面的解决方案不适用于换行字(例如non-violent下图中)生成的框占据一个矩形,覆盖单词的两个部分。相反,使用getClientRects获取呈现相同字符串的所有框,然后将其映射到相同的覆盖层:状态类型:const [highlighst, setHighlights] = useState<DOMRect[] | null>(null);在高亮设置中:return Array.from(range.getBoundingClientRect());渲染图:{highlights &&    highlights.map(({ top, left, width, height }) => (        <span            className='text-highlight'            style={{                top,                left,                width,                height,            }}        ></span>    ))}结果 :我最终能够使用Range API来做到这一点。setStart和方法setEnd可以接受索引变量作为第二个参数。getBoundingClientRect然后,我获取范围本身使用的文本坐标,并将其放入我的状态中。我现在可以将这些值应用到渲染中的固定 div 上:const range = document.createRange();export default function TextNode({ content, footnote }: TextNodeProps) {    const [highlight, setHighlight] = useState<DOMRect | null>(null);    const containerEl = useRef<HTMLSpanElement>(null);    useEffect(() => {        registerText((ev) => {            if (!ev) {                setHighlight(null);                return;            }            if (ev.type === 'sentence') {                (textEl.current as HTMLSpanElement | null)?.scrollIntoView(                    scrollOptions                );            }            if (ev.type === 'word')                setHighlight((old) => {                    const txtNode = containerEl.current?.firstChild as Node;                    range.setStart(txtNode, ev.start);                    range.setEnd(txtNode, ev.end);                    if (!old) {                        (containerEl.current as HTMLSpanElement | null)?.scrollIntoView(                            scrollOptions                        );                    }                    return range.getBoundingClientRect();                });        }, content);    }, [content]);    return (        <span ref={containerEl}>            {content}            {highlight && (                <div                    className='text-highlight'                    style={{                        top: highlight.top,                        left: highlight.left,                        width: highlight.width,                        height: highlight.height,                    }}                ></div>            )}        </span>    );}移动 div 的 CSS :.text-highlight {    position: fixed;    border-bottom: 4px solid blue;    opacity: 0.7;    transition-property: top, left, height, width;    transition-duration: 0.2s;    transform-style: ease-in-out;}
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Html5