高阶函数之光标跟随
如上图:模仿chatgpt 光标文字输出效果
技术要点
1、模仿chatgpt 打字输出效果 循环中使用异步等待 每隔一段时间输出一段文字 代码如下:
async function autoAppend(){ function transfer(text){ let div = document.createElement('div'); let p = document.createElement('p'); p.textContent = text; div.appendChild(p) return div.innerHTML; } function delay(time){ return new Promise(resolve => setTimeout(resolve, time)); } const content = `音频和视频 文件的元 数据可能包含一些 信息如标题、艺术家、专辑等。然而,文件的创建时间通常不存在文件元数据中,而是由文件系统处理 `; for(let i = 0;i<content.length;i++){ let text = content.slice(0,i); let result = transfer(text); //每隔一段时间更新下光标位置 textElem.innerHTML = result updateCursor(); //每隔一段时间添加一个字 //循环中使用异步等待 await delay(300) } }
2、找到最后一个文本节点
function getLastText(node){ if(node.nodeType == Node.TEXT_NODE){ return node; } let childNodes = node.childNodes; for(let i = childNodes.length - 1;i>=0;i--){ let result = getLastText(childNodes[i]); if(result){ return result; } } return null; }
3、在最后一个文本节点加文字
let lastText = getLastText(textElem); const textNode= document.createTextNode('标'); //加文字 if(lastText){ lastText.parentNode.appendChild(textNode); }else{ textElem.appendChild(textNode) }
4、根据最后一个文字设置光标位置
//根据最后一个文字设置光标位置 const range = document.createRange(); range.setStart(textNode,0) range.setEnd(textNode,0); const rect = range.getBoundingClientRect(); const textRect = contaier.getBoundingClientRect(); const x = rect.left - textRect.left; const y = rect.top - textRect.top; cursor.style.transform = `translate(${x}px,${y}px)`;
5、删除文字占位符
textNode.remove()
完整代码如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title></title> <style> .textContainer{ position: relative; width: 800px; height: 800px; border: 1px solid #ddd; margin: 0 auto; white-space: pre-wrap; } .cursor{ position: absolute; width: 10px; height: 10px; /* 添加 transform: translate(0,0) 是指元素当前位置上不做任何移动 在二维空间内对元素进行移动, 但他的移动是针对当前元素的位置进行移动 而不是基于其定位属性移动 */ /* 配置上 top 就是基于定位属性移动了 而不是针对当前元素的位置进行移动*/ transform: translate(0,0); background: #000; border-radius: 50%; animation: blink .5s infinite; top:5px; left: 2px; } @keyframes blink{ 0% {opacity: 1;} 50% {opacity: 0;} 100% {opacity: 1;} } </style> </head> <body> <div class="textContainer"> <div class="text"></div> <div class="cursor"></div> </div> <script> //1、找到最后一个文本节点 //2、加文字 //3、根据文字设置光标位置 //4、删除文字 const contaier = document.querySelector('.textContainer') const textElem = document.querySelector('.text'); const cursor = document.querySelector('.cursor'); async function autoAppend(){ function transfer(text){ let div = document.createElement('div'); let p = document.createElement('p'); p.textContent = text; div.appendChild(p) return div.innerHTML; } function delay(time){ return new Promise(resolve => setTimeout(resolve, time)); } const content = `音频和视频 文件的元 数据可能包含一些 信息如标题、艺术家、专辑等。然而,文件的创建时间通常不存在文件元数据中,而是由文件系统处理 `; for(let i = 0;i<content.length;i++){ let text = content.slice(0,i); let result = transfer(text); //每隔一段时间更新下光标位置 textElem.innerHTML = result updateCursor(); //每隔一段时间添加一个字 //循环中使用异步等待 await delay(300) } } autoAppend(); function getLastText(node){ if(node.nodeType == Node.TEXT_NODE){ return node; } let childNodes = node.childNodes; for(let i = childNodes.length - 1;i>=0;i--){ let result = getLastText(childNodes[i]); if(result){ return result; } } return null; } function updateCursor(){ //1、找到最后一个文本节点 //2、加文字 //3、根据最后一个文字设置光标位置 //4、删除文字 let lastText = getLastText(textElem); const textNode = document.createTextNode('袁'); //加文字 if(lastText){ lastText.parentNode.appendChild(textNode); }else{ textElem.appendChild(textNode) } //根据最后一个文字设置光标位置 const range = document.createRange(); range.setStart(textNode,0) range.setEnd(textNode,0); const rect = range.getBoundingClientRect(); const textRect = contaier.getBoundingClientRect(); const x = rect.left - textRect.left; const y = rect.top - textRect.top; cursor.style.transform = `translate(${x}px,${y}px)`; //删除文字 textNode.remove(); } </script> </body> </html>