1、準備好容器
文字的顯示textRef,以及光標的顯示 ,使用transform-translate對光標進行移動到文字后面
<template><view class="container" ref="contentRef"><u-parse :content="nodeText" ref="textRef"></u-parse><view class="cursor" v-show="cursorShow" :style="{transform:`translate(${x}px,${y}px)`}"></view></view>
</template>
2、準備樣式
樣式,你可以自定義,不必安裝我的,主要是光標的閃爍動畫
<style scoped lang="scss">
.container {position: relative;width: 100%;height: 100vh;box-sizing: border-box;padding: 30rpx 20rpx;.cursor {position: absolute;left: 10rpx;top: 10rpx;width: 30rpx;height: 30rpx;background-color: #000;border-radius: 50%;animation: cursorAnimate 0.5s infinite;}@keyframes cursorAnimate {0% {opacity: 0;}50% {opacity: 1;}100% {opacity: 0;}}
}
</style>
?3、邏輯
1、模擬接受數據
async mockResponse() {this.cursorShow = true;for (let i = 0; i < this.nodeData.length; i++) {try {this.nodeText = this.nodeData.slice(0, i);this.updateCursor();await this.delay(100);} catch (e) {console.log(e)}}this.cursorShow = false;},
2、更新光標位置
updateCursor() {// 1. 找到最后一個文本節點const lastTextNode = this.getLastTextNode(this.$refs.textRef,1);// 2. 創建一個臨時文本節點const tempText = document.createTextNode('\u200B'); // 零寬字符// 3. 將臨時文本節點放在最后一個文本節點之后if (lastTextNode) {lastTextNode.parentNode && lastTextNode.parentNode.appendChild(tempText);} else {this.$refs.textRef && this.$refs.textRef.$el.appendChild(tempText);}// // 4. 獲取臨時文本節點距離父節點的距離(x,y) 可以使用 setStart 和 setEnd 方法來設置 Range 的開始和結束位置。const range = document.createRange(); // 設置范圍range.setStart(tempText, 0);range.setEnd(tempText, 0);const rect = range.getBoundingClientRect(); // 獲取距離信息// // 5. 獲取當前文本容器距離視圖的距離(x,y)const textRect = this.$refs.contentRef && this.$refs.contentRef.$el.getBoundingClientRect();// 6. 獲取到當前文本節點的位置,并將光標的位置插入到相應位置if (textRect) {const x = rect.left - textRect.left + 10;const y = rect.top - textRect.top; // 7.5 是光標高度的一半,為了居中顯示光標this.x = x;this.y = y;}// 7. 移除臨時文本節點tempText.remove();},
4、完整代碼 如下:
<template><view class="container" ref="contentRef"><u-parse :content="nodeText" ref="textRef"></u-parse><view class="cursor" v-show="cursorShow" :style="{transform:`translate(${x}px,${y}px)`}"></view></view>
</template><script>export default {data() {return {nodeData: '打擊好,<p>1. 近日,一份全球數學競賽決賽名單引起廣泛關注。其中,學服裝設計的姜萍,以93分的高分名列第12位。天才少女姜萍的故事在全網引發熱議。總臺記者對江蘇漣水中專黨委書記進行了專訪,揭秘姜萍選擇漣水中專的原因。</p>\n' +' <p>2. 江蘇漣水中專黨委書記介紹,姜萍中考621分,能夠達到當地普通高中的錄取分數線,之所以選擇漣水中專,據姜萍自己講,原因之一是當時她的姐姐以及兩個要好的同學都在這所學校就讀。另外,就是姜萍對服裝專業比較感興趣,認為這里對自己的興趣、愛好發展發揮更有利。</p>',cursorShow: true,nodeText: '',x: 0,y: 0,}},onLoad() {let timer = setInterval(() => {if (this.$refs.textRef) {clearInterval(timer)this.mockResponse()}}, 500)},methods: {delay(time) {return new Promise((resolve) => setTimeout(resolve, time));},/** 模擬請求 */async mockResponse() {this.cursorShow = true;for (let i = 0; i < this.nodeData.length; i++) {try {this.nodeText = this.nodeData.slice(0, i);this.updateCursor();await this.delay(100);} catch (e) {console.log(e)}}this.cursorShow = false;},updateCursor() {// 1. 找到最后一個文本節點const lastTextNode = this.getLastTextNode(this.$refs.textRef,1);// 2. 創建一個臨時文本節點const tempText = document.createTextNode('\u200B'); // 零寬字符// 3. 將臨時文本節點放在最后一個文本節點之后if (lastTextNode) {lastTextNode.parentNode && lastTextNode.parentNode.appendChild(tempText);} else {this.$refs.textRef && this.$refs.textRef.$el.appendChild(tempText);}// // 4. 獲取臨時文本節點距離父節點的距離(x,y) 可以使用 setStart 和 setEnd 方法來設置 Range 的開始和結束位置。const range = document.createRange(); // 設置范圍range.setStart(tempText, 0);range.setEnd(tempText, 0);const rect = range.getBoundingClientRect(); // 獲取距離信息// // 5. 獲取當前文本容器距離視圖的距離(x,y)const textRect = this.$refs.contentRef && this.$refs.contentRef.$el.getBoundingClientRect();// 6. 獲取到當前文本節點的位置,并將光標的位置插入到相應位置if (textRect) {const x = rect.left - textRect.left + 10;const y = rect.top - textRect.top; // 7.5 是光標高度的一半,為了居中顯示光標this.x = x;this.y = y;}// 7. 移除臨時文本節點tempText.remove();},/** 獲取最后一個文本節點 */getLastTextNode(node,index = 1) {if (index === 1) { // 獲取的第一個node,需要查詢$el,childNodes里面的就,不需要了node = node.$el;}if (!node) return null;// console.log(node, node.textContent, node.nodeType, Node.TEXT_NODE)if (node.nodeType === Node.TEXT_NODE && node.textContent?.trim()) {// console.log('返回的', node)return node;}for (let i = node.childNodes.length - 1; i >= 0; i--) {const childNode = node.childNodes[i];const textNode = this.getLastTextNode(childNode,index + 1);if (textNode) {return textNode;}}return null;}}
}
</script><style scoped lang="scss">
.container {position: relative;width: 100%;height: 100vh;box-sizing: border-box;padding: 30rpx 20rpx;.cursor {position: absolute;left: 10rpx;top: 10rpx;width: 30rpx;height: 30rpx;background-color: #000;border-radius: 50%;animation: cursorAnimate 0.5s infinite;}@keyframes cursorAnimate {0% {opacity: 0;}50% {opacity: 1;}100% {opacity: 0;}}
}
</style>