为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

繁星淼淼 2024-01-11 14:16:34
我正在一个 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,然后将其四处移动:包含高亮效果的div直接使用绝对坐标放置。他们如何在只知道字符串索引的情况下访问段落内单词的边界矩形?
查看完整描述

1 回答

?
慕莱坞森

TA贡献1810条经验 获得超4个赞

这是以下解决方案的结果


编辑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下图中)

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

生成的框占据一个矩形,覆盖单词的两个部分。

相反,使用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>

    ))}

结果 :

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

我最终能够使用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;

}


查看完整回答
反对 回复 2024-01-11
  • 1 回答
  • 0 关注
  • 31 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信