textarea 标签显示高亮
一般用 textarea 来输入大段文字之类的, 需求是用户输入之后, 高亮其中的一些, 一般的需求都是给定文字然后渲染出高亮.而这次的需求是在 textarea 上直接显示. 通过转换思路来完成这个事情.
太长不看系列,点击这里可以直接看代码
布局
首先准备 dom 布局
1 2 3 4 5 6 7 8 9 10
| App() { return ( <div className="container"> <div className="backdrop"> <div className="highlights"></div> </div> <textarea></textarea> </div> ); }
|
container 是容器, 包裹子元素, 并提供定位锚点
backdrop 和 highlights 是显示高亮区域
textarea 就是用户输入区域了
样式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| .container { width: 400px; height: 400px; border: 1px solid; position: relative; }
.backdrop, .container textarea { width: 400px; height: 400px; padding: 10px; box-sizing: border-box; position: absolute; }
.highlights { white-space: pre-wrap; word-wrap: break-word; color: transparent; }
.backdrop { z-index: 1; background-color: #fff; overflow: auto; }
.container textarea { font-family: inherit; font-size: 100%; line-height: inherit; color: inherit; background-color: transparent; z-index: 2; }
.backdrop, .container textarea, mark { font-size: 14px; }
mark { color: transparent; background-color: yellow; }
|
添加事件监听
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| export default function App() { const [value, setValue] = React.useState("");
const backdropRef = React.useRef(null); const textareaRef = React.useRef(null); const highlightRef = React.useRef(null);
const handleChange = (e) => { setValue(e.target.value); };
const handleScroll = () => { if (textareaRef.current && backdropRef.current) { const { scrollTop } = textareaRef.current; backdropRef.current.scrollTop = scrollTop; } };
const highlightsText = (s) => { return s.replace(/\n$/g, "\n\n").replace(/[A-Z]/g, "<mark>$&</mark>"); };
React.useEffect(() => { if (highlightRef.current) { highlightRef.current.innerHTML = highlightsText(value); } });
return ( <div className="container"> <div ref={backdropRef} className="backdrop"> <div ref={highlightRef} className="highlights"></div> </div> <textarea onScroll={handleScroll} ref={textareaRef} value={value} onChange={handleChange} ></textarea> </div> ); }
|
性能优化
这里每次用户输入, 都会触发全量的替换和更新, 可以进行优化. 不过一般而言 textarea 数据量不会很大.
- 使用非受控组件, 使用非受控组件直接操作dom来更新读取 textarea
- 优化高亮算法, 可以通过缓存, 比对, 等方法, 也可以使用第三方库, 例如 https://github.com/julmot/mark.js
- 终极解决办法, 虚拟渲染, 类似于无限滚动一样, 只渲染可视区域内的文字