前端老哥必看:input光标处插入字符串的骚操作(附完整代码)

前端老哥必看:input光标处插入字符串的骚操作(附完整代码)
前端老哥必看:input光标处插入字符串的骚操作(附完整代码)
开篇先吐槽这破需求
说实话,我到现在还记得那天下午产品经理晃悠到我工位旁边,一脸轻松地说:“哥,咱们这个输入框啊,得支持在光标位置插入文本,就那种@人的功能,你懂的吧?”
我当时嘴里叼着半根辣条,差点没噎死。
啥?光标位置插入?我脑子里第一反应是:这玩意儿不是挺简单的吗,不就一个input.value += 'xxx'的事?然后产品经理补了一句:“要在当前光标位置插入哦,不能跑到最后面去。”
我当场就愣住了。
辣条也不嚼了,脑子里开始疯狂检索我这些年写过的表单代码。好像…确实没搞过这需求?平时都是直接赋值,谁管你光标在哪啊。但嘴上不能说不会啊,咱是前端,前端不能说不行,只能说好,我研究研究。
等我真坐下来开始撸代码的时候,才发现事情没那么简单。你以为input就是个框,往里怼值就完事了?天真了兄弟。这破玩意儿涉及到选区、焦点、浏览器兼容性、移动端键盘、输入法状态…坑多到能让你怀疑人生。
特别是当你发现不同浏览器对"光标位置"这四个字有各自独特的理解时,那种绝望感,就像你写好的CSS在IE里打开一样酸爽。
但需求已经接了,产品经理的期待眼神还在我脑海里挥之不去。没办法,撸起袖子干吧。这篇文章就是我踩了三天坑之后的心血总结,希望能让你少走点弯路。咱不整那些虚的,直接上干货。
先说结论:selectionStart和selectionEnd是你最好的朋友
在深入代码之前,我得先把这俩API给你讲明白,因为后面所有的操作都围着它俩转。
selectionStart和selectionEnd是HTMLInputElement和HTMLTextAreaElement的原生属性。这俩货告诉你用户当前在输入框里选了啥,或者说光标在哪杵着。
// 先拿个input元素过来const input = document.getElementById('myInput');// 看看当前选区的起始位置 console.log(input.selectionStart);// 比如输出 5// 看看选区的结束位置 console.log(input.selectionEnd);// 比如输出 8如果selectionStart和selectionEnd的值一样,比如都是5,那就说明用户没在选文字,光标就孤零零地停在第5个字符后面。如果不一样,比如5和8,那就是从第5位选到了第8位,选中了3个字符。
这俩属性现代浏览器都支持,包括textarea和input[type=“text”]、input[type=“password”]这些。但注意啊,input[type=“number”]这货有点特殊,有些浏览器不让用这API,所以如果你要做数字输入框的光标插入,建议还是用type="text"然后自己校验数字,省得给自己找麻烦。
// 先判断一下支不支持,养成好习惯functionisSelectionSupported(element){returntypeof element.selectionStart ==='number'&&typeof element.selectionEnd ==='number';}// 用起来就放心多了const input = document.querySelector('input');if(isSelectionSupported(input)){// 放心大胆地干 console.log('光标在:', input.selectionStart);}else{// 老古董浏览器,直接往末尾追加吧,爱咋咋地 console.warn('这浏览器太老了,selection API都不支持');}还有个selectionDirection,告诉你选区是从左往右选的还是从右往左选的,不过一般用不上,知道有这玩意就行。
核心代码其实就那几行
好了,原理讲完了,上正菜。在光标位置插入文本的核心逻辑就四步:
- 拿到当前光标位置(selectionStart)
- 把原字符串切成两半:光标前和光标后
- 中间插入新内容
- 把光标挪到插入内容后面,让用户能继续打字
看代码:
/** * 在光标位置插入文本 * @param {HTMLInputElement|HTMLTextAreaElement} input - 输入框元素 * @param {string} text - 要插入的文本 * @param {boolean} moveCursor - 是否移动光标到插入文本之后,默认true */functioninsertTextAtCursor(input, text, moveCursor =true){// 安全第一,先检查支持性if(!isSelectionSupported(input)){// 不支持的话直接追加到末尾,总比报错强 input.value += text;return;}// 记录当前光标位置const start = input.selectionStart;const end = input.selectionEnd;// 拿到当前值const originalValue = input.value;// 切成三段:前面 + 插入内容 + 后面const before = originalValue.substring(0, start);const after = originalValue.substring(end);// 注意这里用end,如果有选中文本要替换掉// 组装新值const newValue = before + text + after;// 赋值 input.value = newValue;// 关键步骤:重新设置光标位置if(moveCursor){const newCursorPos = start + text.length; input.selectionStart = newCursorPos; input.selectionEnd = newCursorPos;}// 触发input事件,让Vue、React这些框架知道值变了 input.dispatchEvent(newEvent('input',{bubbles:true}));}就这么几行,但里面的门道不少。我逐行给你解释:
为什么要用substring(end)而不是substring(start)?
因为用户可能选中了文本。比如输入框里是"hello world",用户选中了"world"(start=6, end=11),这时候插入"Kimi",应该变成"hello Kimi",而不是"hello Kimiworld"。所以后半段要从end开始切,把选中的内容替换掉。
为什么要手动设置selectionStart和End?
因为直接给input.value赋值后,浏览器会把光标扔到末尾去。你想想,用户本来在中间打字,插了个@用户名,结果光标跑最后去了,还得手动点回来,这体验能忍?所以必须手动把光标挪到插入内容后面。
为什么要dispatchEvent?
因为现代框架(Vue、React、Angular)都监听input事件来做双向绑定。你直接改value,框架不知道值变了,数据不同步,后面会出各种诡异bug。手动触发个input事件,告诉框架:“嘿,我改了,你也更新一下”。
来,看个完整的例子:
<!DOCTYPEhtml><htmllang="zh-CN"><head><metacharset="UTF-8"><title>光标插入演示</title><style>.container{max-width: 600px;margin: 50px auto;padding: 20px;}textarea{width: 100%;height: 150px;padding: 10px;font-size: 14px;}.btn-group{margin-top: 10px;}button{margin-right: 10px;padding: 8px 16px;cursor: pointer;}.info{margin-top: 10px;color: #666;font-size: 12px;}</style></head><body><divclass="container"><h3>在光标处插入文本演示</h3><textareaid="editor"placeholder="在这打字,然后点下面的按钮..."></textarea><divclass="btn-group"><buttononclick="insertEmoji('😀')">插入😀</button><buttononclick="insertEmoji('🎉')">插入🎉</button><buttononclick="insertEmoji('[图片]')">插入[图片]</button><buttononclick="insertAtUser()">@某人</button></div><divclass="info"> 当前光标位置: <spanid="cursorPos">0</span> | 选区: <spanid="selection">无</span></div></div><script>const editor = document.getElementById('editor');const cursorPosSpan = document.getElementById('cursorPos');const selectionSpan = document.getElementById('selection');// 实时显示光标位置,方便观察 editor.addEventListener('keyup', updateCursorInfo); editor.addEventListener('click', updateCursorInfo); editor.addEventListener('selectionchange', updateCursorInfo);// 部分浏览器支持functionupdateCursorInfo(){ cursorPosSpan.textContent =`${editor.selectionStart} - ${editor.selectionEnd}`;if(editor.selectionStart !== editor.selectionEnd){const selectedText = editor.value.substring(editor.selectionStart, editor.selectionEnd); selectionSpan.textContent =`选中"${selectedText}"`;}else{ selectionSpan.textContent ='无';}}functioninsertEmoji(emoji){insertTextAtCursor(editor, emoji);updateCursorInfo(); editor.focus();// 保持焦点}functioninsertAtUser(){// 模拟@功能,实际项目中可能是弹个选择框const userName ='张三';const insertText =`@${userName}`;// 后面加个空格,方便继续打字insertTextAtCursor(editor, insertText);updateCursorInfo(); editor.focus();}// 就是上面讲的那套逻辑functioninsertTextAtCursor(input, text){if(typeof input.selectionStart !=='number'){ input.value += text;return;}const start = input.selectionStart;const end = input.selectionEnd;const value = input.value; input.value = value.substring(0, start)+ text + value.substring(end);const newPos = start + text.length; input.selectionStart = newPos; input.selectionEnd = newPos;// 通知框架 input.dispatchEvent(newEvent('input',{bubbles:true}));}</script></body></html>你把这段代码保存成html文件打开试试,在textarea里随便打点字,然后把光标放中间,点插入按钮,看看光标是不是乖乖待在插入内容后面。这就是我们要的效果。
不同浏览器给你整的幺蛾子
写前端最烦啥?兼容性问题。这功能在Chrome里跑得好好的,到Safari里可能就抽风,到IE里直接躺平。
Chrome/Edge/Firefox:这三个算是比较老实的,基本按W3C标准来,selection API支持得挺好。但也要注意版本,特别老的Chrome(比如60以下)可能会有点小毛病,不过现在应该没人用那么老的版本了。
Safari:苹果的浏览器总是有点自己的想法。桌面版Safari问题不大,但iOS Safari就精彩了。特别是当虚拟键盘弹出来的时候,光标位置可能会飘。有时候你明明在input.selectionStart读到了位置是5,但视觉上光标好像在第3位,这就是iOS的锅。
IE家族:IE11及以下版本直接不支持selectionStart/End。对,你没看错,直接undefined。所以如果你公司还要支持IE(比如某些国企、银行项目),得准备降级方案。
移动端输入法:这是个大坑。安卓机的第三方输入法(搜狗、百度、讯飞这些)在输入过程中会进入"合成状态"(composition),这时候selectionStart可能返回0或者不准。还有那种联想输入、滑动输入,都会导致光标位置混乱。
来,给你个兼容性更好的版本:
/** * 兼容性更好的光标插入函数 * 处理了IE、移动端、合成状态等情况 */functioninsertTextRobust(input, text){// 先聚焦,确保有光标 input.focus();// 现代浏览器if(typeof input.selectionStart ==='number'){const start = input.selectionStart;const end = input.selectionEnd;const value = input.value; input.value = value.substring(0, start)+ text + value.substring(end);// 设置新光标位置const newPos = start + text.length;// 用setTimeout确保在移动端键盘弹出后也能正确设置setTimeout(()=>{ input.selectionStart = input.selectionEnd = newPos;},0); input.dispatchEvent(newEvent('input',{bubbles:true}));return;}// IE6-10的降级方案,用TextRangeif(document.selection && document.selection.createRange){const range = document.selection.createRange(); range.text = text;// 直接替换选区文本// 移动光标到插入内容后面 range.collapse(false);// false表示移到末尾 range.select(); input.dispatchEvent(newEvent('input',{bubbles:true}));return;}// 终极降级:直接追加 input.value += text;}// 针对移动端的特殊处理functionisComposing(input){// 检查是否处于输入法合成状态return input.isComposing ===true;}// 监听合成事件,避免在拼音输入过程中插入 editor.addEventListener('compositionstart',()=>{ editor.isComposing =true;}); editor.addEventListener('compositionend',()=>{ editor.isComposing =false;});// 使用时判断一下functionsafeInsert(input, text){if(isComposing(input)){// 等合成结束再插入,或者提示用户 console.warn('正在输入中,请稍后再试');return;}insertTextRobust(input, text);}看到没,就一个简单的插入功能,为了兼容各种奇葩环境,代码量直接翻倍。这就是前端开发的日常,表面上光鲜亮丽,背地里各种if-else判断浏览器。
实际项目里这些场景逃不掉
光讲API没意思,给你看看实际项目中哪些地方会用到这套技术。你会发现,原来那些看起来高大上的功能,底层都是这么个原理。
场景一:聊天框@人
这是最常见的需求了。微信、钉钉、飞书,哪个没有@功能?实现思路就是:
- 用户输入@或者点击@按钮
- 弹出成员选择列表
- 选择后,在光标位置插入"@用户名 "
- 用户名要高亮显示(这个得用contenteditable,普通input做不到高亮)
// 简化版@功能classAtFunctionality{constructor(inputElement){this.input = inputElement;this.atList =['张三','李四','王五','赵六'];}showAtList(){// 显示选择框,实际项目中可能是个弹层 console.log('可选成员:',this.atList);}selectUser(userName){const insertText =`@${userName}`;// 插入文本const start =this.input.selectionStart;const end =this.input.selectionEnd;const value =this.input.value;this.input.value = value.substring(0, start)+ insertText + value.substring(end);// 光标后移const newPos = start + insertText.length;this.input.setSelectionRange(newPos, newPos);// 触发事件this.input.dispatchEvent(newEvent('input',{bubbles:true}));this.input.focus();// 实际项目中这里还要记录@的用户ID,方便后续发消息时解析this.recordAtUser(userName, start);}recordAtUser(name, position){// 记录@信息,发消息时要用 console.log(`在位置${position}@了${name}`);}}// 使用const chatInput = document.getElementById('chatInput');const atFunc =newAtFunctionality(chatInput);// 监听@键 chatInput.addEventListener('keyup',(e)=>{if(e.key ==='@'){ atFunc.showAtList();}});场景二:代码编辑器的自动补全
VS Code、CodePen这些编辑器,你打个<div,然后按Tab,自动给你补全成<div></div>,光标还在中间。这背后的逻辑也是操作selection。
functionautoComplete(input, before, after, placeholder =''){const start = input.selectionStart;const end = input.selectionEnd;const value = input.value;// 插入前后缀const newValue = value.substring(0, start)+ before + placeholder + after + value.substring(end); input.value = newValue;// 光标放到placeholder位置,方便用户直接输入const cursorStart = start + before.length;const cursorEnd = cursorStart + placeholder.length; input.setSelectionRange(cursorStart, cursorEnd); input.focus(); input.dispatchEvent(newEvent('input',{bubbles:true}));}// 使用示例:HTML标签补全 document.getElementById('codeEditor').addEventListener('keydown',(e)=>{if(e.key ==='Tab'){ e.preventDefault();// 阻止默认的失焦行为const input = e.target;const before = input.selectionStart;const currentWord =getCurrentWord(input);// 假设有个函数能拿到当前单词if(currentWord ==='div'){autoComplete(input,'<div>','</div>','内容');}elseif(currentWord ==='span'){autoComplete(input,'<span>','</span>','');}}});场景三:表单自动格式化
比如手机号输入,自动加空格分隔:138 1234 5678。或者银行卡号,每四位一个空格。这种需求也是光标插入的变种,只不过插入的是空格或者横杠。
functionformatPhoneNumber(input){const value = input.value.replace(/\s/g,'');// 去掉所有空格let formatted ='';// 按规则插入空格if(value.length >3){ formatted += value.substring(0,3)+' ';if(value.length >7){ formatted += value.substring(3,7)+' '+ value.substring(7);}else{ formatted += value.substring(3);}}else{ formatted = value;}// 记录光标相对位置const oldPos = input.selectionStart;const beforeLength = input.value.length; input.value = formatted;// 计算新光标位置(考虑新增的空格)const addedSpaces = formatted.length - beforeLength;const newPos = oldPos + addedSpaces; input.setSelectionRange(newPos, newPos);}场景四:富文本编辑器
Draft.js、Quill、TinyMCE这些富文本编辑器,底层也是操作selection和range,只不过它们操作的是DOM节点而不是纯文本。原理是一样的:获取选区、插入内容、恢复选区。
场景五:评论区插入表情
这个和@人很像,只不过插入的是表情符号或者图片占位符。
functioninsertEmoji(input, emojiCode){// emojiCode可能是":smile:"这种,需要转换成实际字符😀const emojiMap ={':smile:':'😀',':cry:':'😢',':heart:':'❤️'};const emoji = emojiMap[emojiCode]|| emojiCode;// 插入insertTextAtCursor(input, emoji);// 可以加个动画效果,让用户知道插入了showInsertAnimation(input);}functionshowInsertAnimation(input){// 视觉反馈,比如闪一下 input.style.backgroundColor ='#fff3cd';setTimeout(()=>{ input.style.backgroundColor ='';},200);}踩坑之后的血泪排查经验
写了这么多年代码,踩的坑比吃的盐还多。这里分享几个我血淋淋的教训,希望你别重蹈覆辙。
坑一:异步更新导致光标位置不对
有次我在React里用setState更新值,然后立刻设置光标位置,结果发现光标总是不对。后来才反应过来,setState是异步的,值还没更新到DOM里,我就去设置光标了,当然有问题。
// 错误示范consthandleInsert=(text)=>{const newValue = value.slice(0, cursor)+ text + value.slice(cursor);setValue(newValue);// 异步的!// 这时候DOM还没更新,设置光标无效 inputRef.current.setSelectionRange(newPos, newPos);};// 正确做法:用useEffect监听值变化后再设置光标useEffect(()=>{if(inputRef.current && pendingCursorPos !==null){ inputRef.current.setSelectionRange(pendingCursorPos, pendingCursorPos);setPendingCursorPos(null);}},[value]);// 或者用setTimeout hack一下(不推荐,但应急可用)setTimeout(()=>{ input.setSelectionRange(newPos, newPos);},0);坑二:value改了但光标没动
有时候你直接input.value = 'xxx',发现光标跑到最前面去了。这是因为赋值操作会重置选区。记得赋值后手动设置selectionStart和End。
坑三:移动端focus之后键盘弹起,光标跑到开头
iOS Safari特别容易出现这问题。用户点击输入框,键盘弹起来,这时候你插入文本,光标可能在0位置而不是用户之前的位置。
解决方案是:在插入前记录光标位置,插入后用setTimeout延迟设置光标。
functioninsertOnMobile(input, text){// 先记录const savedStart = input.selectionStart;const savedEnd = input.selectionEnd;// 插入const value = input.value; input.value = value.substring(0, savedStart)+ text + value.substring(savedEnd);// 延迟设置,等键盘稳定requestAnimationFrame(()=>{const newPos = savedStart + text.length; input.setSelectionRange(newPos, newPos);});}坑四:用setRangeText可以简化代码
其实现代浏览器提供了setRangeText方法,可以一行代码搞定插入:
input.setRangeText(text, start, end,'end');// 第四个参数'end'表示插入后光标在插入文本之后// 还可以选'start'(在之前)、'select'(选中插入的文本)、'preserve'(保持原样)这个方法的好处是原子操作,不会触发多余的事件,而且自动处理光标。但兼容性比手动操作稍差,IE全系列不支持,部分移动端浏览器也不支持。所以如果你想用,得准备好降级方案。
functionmodernInsert(input, text){if(typeof input.setRangeText ==='function'){// 现代浏览器,一行搞定 input.setRangeText(text, input.selectionStart, input.selectionEnd,'end'); input.dispatchEvent(newEvent('input',{bubbles:true}));}else{// 降级到手动操作insertTextAtCursor(input, text);}}坑五:CSS样式影响光标视觉位置
有次我遇到个诡异问题:代码逻辑没错,光标位置设置也对,但用户就是觉得光标位置不对。查了半天,发现是字体搞的鬼。输入框用了等宽字体,但计算位置时按普通字体算的,导致视觉偏差。
还有letter-spacing、text-indent这些样式,都会影响光标的实际显示位置。如果你发现光标位置对不上,先检查CSS,别死磕JS代码。
坑六:控制台打印selectionStart看看是不是你期望的值
调试技巧:遇到光标问题,先在控制台打印input.selectionStart和input.selectionEnd,看看值是不是你预期的。有时候你以为光标在第5位,实际在第0位,这时候插入当然不对。
// 调试代码,随时看看光标在哪setInterval(()=>{const input = document.activeElement;if(input && input.tagName ==='TEXTAREA'){ console.log('Cursor at:', input.selectionStart,'Selection:', input.value.substring(input.selectionStart, input.selectionEnd));}},2000);让代码更优雅的几条野路子
基础功能实现了,但代码写得太乱以后维护是灾难。这里分享几个让代码更优雅的思路。
封装成工具函数,别到处复制粘贴
把插入逻辑封装成一个独立的工具函数,项目里哪里需要哪里调。别在每个组件里都写一遍那几行切割字符串的代码,万一要改逻辑,得改十几个地方,漏一个就bug。
// utils/cursor.jsexportconst CursorUtils ={/** * 在光标处插入文本 */insertText(element, text, options ={}){const{ moveCursor =true, replaceSelection =true,// 是否替换选中的文本 triggerInput =true// 是否触发input事件}= options;if(!this.isSupported(element)){ element.value += text;return;}const start = element.selectionStart;const end = element.selectionEnd;const value = element.value;const before = value.substring(0, start);const after = replaceSelection ? value.substring(end): value.substring(start); element.value = before + text + after;if(moveCursor){const newPos = start + text.length;this.setCursorPosition(element, newPos);}if(triggerInput){this.triggerInputEvent(element);}return{newValue: element.value,cursorPosition: start + text.length };},/** * 设置光标位置 */setCursorPosition(element, position){if(typeof element.setSelectionRange ==='function'){ element.setSelectionRange(position, position);}elseif(element.createTextRange){// IEconst range = element.createTextRange(); range.move('character', position); range.select();}},/** * 获取当前选区文本 */getSelectedText(element){if(!this.isSupported(element))return'';return element.value.substring(element.selectionStart, element.selectionEnd);},/** * 替换选区文本 */replaceSelection(element, text){returnthis.insertText(element, text,{replaceSelection:true});},/** * 检查是否支持Selection API */isSupported(element){returntypeof element.selectionStart ==='number';},/** * 触发input事件 */triggerInputEvent(element){const event =newEvent('input',{bubbles:true}); element.dispatchEvent(event);}};// 使用import{ CursorUtils }from'./utils/cursor';// 在组件里直接调用 CursorUtils.insertText(myInput,'@张三 ');React里怎么处理
React的受控组件有点特殊,因为值存在state里,直接操作DOM会被React的渲染覆盖。
import React,{ useRef, useState, useEffect }from'react';functionSmartInput(){const[value, setValue]=useState('');const[cursorPosition, setCursorPosition]=useState(null);const inputRef =useRef(null);// 监听cursorPosition变化,设置真实光标useEffect(()=>{if(cursorPosition !==null&& inputRef.current){ inputRef.current.setSelectionRange(cursorPosition, cursorPosition);setCursorPosition(null);// 重置}},[cursorPosition, value]);consthandleInsert=(insertText)=>{const input = inputRef.current;const start = input.selectionStart;const end = input.selectionEnd;// 计算新值const newValue = value.substring(0, start)+ insertText + value.substring(end);// 计算新光标位置const newPos = start + insertText.length;// 更新statesetValue(newValue);setCursorPosition(newPos);// 记录,等useEffect里设置};return(<input ref={inputRef} value={value} onChange={(e)=>setValue(e.target.value)}/>);}Vue里怎么处理
Vue的v-model也会干扰,建议用ref直接操作DOM,绕过v-model的语法糖。
<template> <textarea ref="myInput" v-model="content"></textarea> <button @click="insertAtCursor">插入</button> </template> <script> export default { data() { return { content: '' }; }, methods: { insertAtCursor() { const input = this.$refs.myInput; const text = '[插入的内容]'; // 直接操作DOM,Vue会检测到变化并同步到data const start = input.selectionStart; const end = input.selectionEnd; this.content = this.content.substring(0, start) + text + this.content.substring(end); // 等DOM更新后设置光标 this.$nextTick(() => { const newPos = start + text.length; input.setSelectionRange(newPos, newPos); input.focus(); }); } } }; </script> 批量插入的性能优化
如果你要一次性插入很多内容(比如粘贴一大段文本,里面包含多个需要自动替换的标记),别每次都操作DOM,攒一波再更新。
functionbatchInsert(input, insertions){// insertions是个数组,[{position: 5, text: 'xxx'}, {position: 10, text: 'yyy'}]// 注意要按位置倒序排序,这样插入不会影响后面的位置const sorted = insertions.sort((a, b)=> b.position - a.position);let value = input.value; sorted.forEach(({position, text})=>{ value = value.substring(0, position)+ text + value.substring(position);}); input.value = value;// 最后设置一次光标const lastInsertion = sorted[sorted.length -1];const finalPos = lastInsertion.position + lastInsertion.text.length; input.setSelectionRange(finalPos, finalPos); input.dispatchEvent(newEvent('input',{bubbles:true}));}加点动画让用户感知
插入内容后,可以加个微小的动画,让用户明确知道发生了什么。特别是插入的是表情、图片这种非文本内容时。
functioninsertWithAnimation(input, text){// 先插入insertTextAtCursor(input, text);// 高亮一下刚插入的内容(需要包装成span,普通input做不到,textarea也做不到,得用contenteditable)// 这里简单做个输入框闪烁 input.animate([{backgroundColor:'transparent'},{backgroundColor:'#fff3cd'},{backgroundColor:'transparent'}],{duration:300,iterations:1});}最后说点掏心窝子的话
写到这儿,估计你也看出来了,就一个"在光标处插入文本"的小功能,真要做好了,里面门道多着呢。这大概就是前端开发的日常吧,看似简单的东西,深挖下去全是坑。
我刚开始接到这个需求的时候,以为半小时搞定,结果折腾了三天。第一天写基础功能,第二天处理浏览器兼容性,第三天解决移动端的各种诡异问题。特别是iOS Safari,那光标位置飘忽不定的劲儿,差点让我怀疑人生。
所以啊,几个建议送给你:
多换几个浏览器测试。别只在你心爱的Chrome里测完就完事了,至少得看看Safari、Firefox、Edge。如果用户群体里有用IE的(虽然2024年了应该很少了),那还得准备降级方案。
移动端一定要真机测试。模拟器不靠谱,特别是涉及键盘、光标的交互,必须在真机上点一点。安卓机多测几个品牌,华为、小米、OPPO、vivo,它们的系统定制程度不同,表现也可能不一样。
代码写好注释。你现在觉得逻辑很清晰,三个月后回来看,绝对一脸懵逼。特别是那些兼容性处理的代码,为什么要加setTimeout,为什么要判断isComposing,写清楚,不然以后自己都想抽自己。
MDN文档比你想象的有用。遇到不确定的API行为,别瞎猜,去MDN查。selection API的文档写得很详细,还有各种浏览器兼容性表格,能省你很多试错时间。
找个成熟库也不丢人。如果你做的功能很复杂,比如完整的@功能、富文本编辑,别硬自己写,找个成熟的库(比如Tribute.js做@功能,Quill做富文本)。早点下班不香吗?老板又不会因为你用了库而扣你工资,他只看功能能不能用。
好了,就写到这儿吧。希望这篇文章能帮你少踩几个坑。如果看完你还是搞不定…那要不考虑转后端?开玩笑的,前端虐我千百遍,我待前端如初恋。加油,兄弟!
欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。
推荐:DTcode7的博客首页。
一个做过前端开发的产品经理,经历过睿智产品的折磨导致脱发之后,励志要翻身农奴把歌唱,一边打入敌人内部一边持续提升自己,为我们广大开发同胞谋福祉,坚决抵制睿智产品折磨我们码农兄弟!
| 专栏系列(点击解锁) | 学习路线(点击解锁) | 知识定位 |
|---|---|---|
| 《微信小程序相关博客》 | 持续更新中~ | 结合微信官方原生框架、uniapp等小程序框架,记录请求、封装、tabbar、UI组件的学习记录和使用技巧等 |
| 《AIGC相关博客》 | 持续更新中~ | AIGC、AI生产力工具的介绍,例如stable diffusion这种的AI绘画工具安装、使用、技巧等总结 |
| 《HTML网站开发相关》 | 《前端基础入门三大核心之html相关博客》 | 前端基础入门三大核心之html板块的内容,入坑前端或者辅助学习的必看知识 |
| 《前端基础入门三大核心之JS相关博客》 | 前端JS是JavaScript语言在网页开发中的应用,负责实现交互效果和动态内容。它与HTML和CSS并称前端三剑客,共同构建用户界面。 通过操作DOM元素、响应事件、发起网络请求等,JS使页面能够响应用户行为,实现数据动态展示和页面流畅跳转,是现代Web开发的核心 | |
| 《前端基础入门三大核心之CSS相关博客》 | 介绍前端开发中遇到的CSS疑问和各种奇妙的CSS语法,同时收集精美的CSS效果代码,用来丰富你的web网页 | |
| 《canvas绘图相关博客》 | Canvas是HTML5中用于绘制图形的元素,通过JavaScript及其提供的绘图API,开发者可以在网页上绘制出各种复杂的图形、动画和图像效果。Canvas提供了高度的灵活性和控制力,使得前端绘图技术更加丰富和多样化 | |
| 《Vue实战相关博客》 | 持续更新中~ | 详细总结了常用UI库elementUI的使用技巧以及Vue的学习之旅 |
| 《python相关博客》 | 持续更新中~ | Python,简洁易学的编程语言,强大到足以应对各种应用场景,是编程新手的理想选择,也是专业人士的得力工具 |
| 《sql数据库相关博客》 | 持续更新中~ | SQL数据库:高效管理数据的利器,学会SQL,轻松驾驭结构化数据,解锁数据分析与挖掘的无限可能 |
| 《算法系列相关博客》 | 持续更新中~ | 算法与数据结构学习总结,通过JS来编写处理复杂有趣的算法问题,提升你的技术思维 |
| 《IT信息技术相关博客》 | 持续更新中~ | 作为信息化人员所需要掌握的底层技术,涉及软件开发、网络建设、系统维护等领域的知识 |
| 《信息化人员基础技能知识相关博客》 | 无论你是开发、产品、实施、经理,只要是从事信息化相关行业的人员,都应该掌握这些信息化的基础知识,可以不精通但是一定要了解,避免日常工作中贻笑大方 | |
| 《信息化技能面试宝典相关博客》 | 涉及信息化相关工作基础知识和面试技巧,提升自我能力与面试通过率,扩展知识面 | |
| 《前端开发习惯与小技巧相关博客》 | 持续更新中~ | 罗列常用的开发工具使用技巧,如 Vscode快捷键操作、Git、CMD、游览器控制台等 |
| 《photoshop相关博客》 | 持续更新中~ | 基础的PS学习记录,含括PPI与DPI、物理像素dp、逻辑像素dip、矢量图和位图以及帧动画等的学习总结 |
| 日常开发&办公&生产【实用工具】分享相关博客》 | 持续更新中~ | 分享介绍各种开发中、工作中、个人生产以及学习上的工具,丰富阅历,给大家提供处理事情的更多角度,学习了解更多的便利工具,如Fiddler抓包、办公快捷键、虚拟机VMware等工具 |
吾辈才疏学浅,摹写之作,恐有瑕疵。望诸君海涵赐教。望轻喷,嘤嘤嘤
非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。愿斯文对汝有所裨益,纵其简陋未及渊博,亦足以略尽绵薄之力。倘若尚存阙漏,敬请不吝斧正,俾便精进!
