1. IE 6/7/8 里光标位置为什么这么难拿如果你还在维护一套必须跑在 IE 6/7/8 上的老后台系统大概率遇到过这个需求用户在文本框里打字你要实时知道光标在第几个字符用来做插入表情、插入变量、 提醒或者字数截断。现代浏览器里一行input.selectionStart就完事了但在 IE 6/7/8 上selectionStart这个属性根本不存在读出来是undefined。IE 6/7/8 走的是另一套 DOM 选区模型核心是document.selection和TextRange对象。它没有「起点终点索引」这种直观概念只有一段一段的文本范围。你要拿光标位置本质上是在做一道减法题把「从文本框开头到光标处」的文本长度算出来。微软当年给TextRange提供了moveToElementText和setEndPoint这两个方法正好能拼出这个长度。这篇面向的是需要兼容旧版 IE 页面的前端开发者。我会先讲清楚TextRange那套 API 的原理再给出一份可复制的 TaoToken 统一 Key/API 通道配置骨架settings.json/config.toml最后在 IE 各版本里一步步验证光标位置读取结果。TaoToken 在这里的作用是当你想用大模型帮你批量生成或审查这些兼容代码时通过一个统一的 API 通道调用不用在多个平台之间来回切 Key。官网入口是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 。先把原理说透。document.selection.createRange()拿到的是当前页面里用户选中的那段范围。如果焦点在文本框内这个 range 就对应文本框里的选区。range.duplicate()复制一份副本因为原 range 后面还要用不能直接改。副本调用moveToElementText(obj)之后会把副本的范围扩展到整个文本框的全部内容。接着setEndPoint(EndToEnd, range)把副本的结束点挪到原 range 的结束点也就是光标位置。这时候副本覆盖的就是「从文本框开头到光标」这段文本它的text.length就是光标索引。关键点在于moveToElementText只对元素节点有效所以传进去的必须是文本框 DOM 本身不能是 jQuery 包装对象。另外 IE 下必须先obj.focus()否则document.selection可能指向页面其他位置拿到的 range 是错的。这两点踩过坑的人不少。2. TaoToken 前置统一 Key 与 API 通道准备在动手写兼容代码之前先把调用通道配好。TaoToken 提供的是一个统一的 API 入口你申请一个 Key就能通过同一套地址调用不同模型省得每个平台单独维护密钥。对于「让模型帮我审查 IE 兼容代码」这种场景统一通道能明显减少配置成本。第一步是拿 Key。打开控制台 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 登录后进入 API Keys 页面 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 新建一个 Key 并复制保存。这个 Key 只在创建时完整显示一次丢了就得重建。第二步是确认你要用的模型名。不同模型在兼容性代码审查上的表现不一样有的擅长解释老 API有的擅长直接给可运行片段。你可以在模型对话页 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentchatutm_campaignrewrite 里先试几轮确认哪个模型对你的 IE 代码理解得准再写进配置文件。第三步是决定用哪种接入方式。如果你只是偶尔问几句用对话页就够了如果你要把调用嵌进构建脚本、批量审查老代码那就走 API。API 基础地址是 https://taotoken.net/api 注意这个地址不带 UTM 参数直接填就行。如果你长期做编码类任务比如让模型持续帮你重构 IE 兼容层可以看看 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 它更适合高频、长周期的编码场景。这里要提醒一句TaoToken 是 API 通道不是编辑器替代品。你的代码还是在本地 IDE 里写TaoToken 负责的是模型调用这一层。别把它当成能直接改你文件的工具。3. 可复制配置settings.json 与 config.toml 骨架下面给两份配置骨架一份给用 VS Code 系插件或 Node 脚本的场景一份给用 Python 工具链的场景。你按自己实际用的填Key 和模型名换成自己的。先看settings.json。这份适合放在项目根目录或者你用的编码助手读取的配置路径下{ taotoken: { api_base: https://taotoken.net/api, api_key: sk-你的Key填这里, model: 你选定的模型名, timeout_ms: 60000, max_retries: 2, temperature: 0.2 }, ie_compat_review: { target_versions: [IE6, IE7, IE8], check_items: [ selectionStart 是否存在, document.selection 是否可用, moveToElementText 调用对象, focus 调用时机 ] } }temperature设成 0.2 是因为代码审查要稳定别让模型自由发挥。timeout_ms给 60 秒老代码片段长的时候不至于断。再看config.toml适合 Python 侧[taotoken] api_base https://taotoken.net/api api_key sk-你的Key填这里 model 你选定的模型名 timeout 60 max_retries 2 [taotoken.generation] temperature 0.2 top_p 0.9 [ie_compat] versions [IE6, IE7, IE8] strict_mode true两份配置的核心字段一致api_base固定填https://taotoken.net/apiapi_key填你刚建的 Keymodel填你在对话页验证过的模型名。strict_mode打开后模型会更倾向于指出「这段代码在 IE6 下会报错」而不是含糊带过。配置写好后建议先用一个最小请求验证通道通不通别等写了一大段代码才发现 Key 填错了。验证方法在下一节。4. 验证请求与 IE 各版本光标读取结果先验证 API 通道。用 curl 发一个最小请求curl -X POST https://taotoken.net/api/v1/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer sk-你的Key填这里 \ -d { model: 你选定的模型名, messages: [ {role: user, content: 用一句话说明 IE8 下 document.selection.createRange 的作用} ], temperature: 0.2 }如果返回里有正常的choices内容说明 Key 和地址都对。返回 401 就是 Key 错了返回 404 多半是api_base后面多写或少写了路径。通道通了之后回到 IE 兼容代码本身。下面这份是整理后的可运行版本我加了版本判断和容错function getCursorPos(obj) { var caretPos 0; // 现代浏览器分支 if (typeof obj.selectionStart number) { caretPos obj.selectionStart; return caretPos; } // IE 6/7/8 分支 if (document.selection) { obj.focus(); var range document.selection.createRange(); if (!range) { return 0; } var storedRange range.duplicate(); storedRange.moveToElementText(obj); storedRange.setEndPoint(EndToEnd, range); caretPos storedRange.text.length - range.text.length; // 修正setEndPoint 后副本长度即为光标前文本长度 caretPos storedRange.text.length; } return caretPos; }注意这里有个容易搞混的地方。原始写法里用了stored_range.text.length - range.text.length但setEndPoint(EndToEnd, range)之后副本的结束点已经对齐到原 range 的结束点副本覆盖的就是「开头到光标」这段直接取storedRange.text.length就是光标位置。两种写法在「无选中」时结果一致但在「有选中文本」时含义不同前者算的是选区起点后者算的是选区终点。你要根据业务决定用哪个。做插入功能一般要选区起点那就保留减法写法。在 IE 各版本里验证的步骤在 IE6 里打开一个含文本框的测试页把上面函数贴进控制台或脚本块。在文本框里输入abcdef把光标点到c和d之间调用getCursorPos(document.getElementById(txt))应该返回3。选中cd两个字符再调用减法写法返回2直接取长度写法返回4。IE7 的操作一样但要注意 IE7 的document.selection在某些 iframe 场景下会返回外层文档的选区所以测试页别套 iframe。IE8 默认走标准模式document.selection仍然可用但如果页面声明了X-UA-Compatible为IEedge行为会偏向现代模式selectionStart可能已经存在这时会走第一个分支结果同样正确。三个版本都验证通过后把结果记下来。如果你想让模型帮你检查还有没有遗漏的边界情况可以把这段代码贴到模型对话页 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentchatutm_campaignrewrite 让它针对 IE6 的moveToElementText异常情况给建议。5. 本篇常见错排查报错一moveToElementText不是函数或参数无效。最常见原因是传进去的是 jQuery 对象。$(#txt)返回的是包装对象不是 DOM 元素。改成$(#txt)[0]或document.getElementById(txt)。另一个原因是传了input之外的元素比如divmoveToElementText对非文本容器行为不一致。报错二光标位置总是 0。检查有没有先调obj.focus()。IE 下如果焦点不在文本框document.selection.createRange()拿到的是页面级选区setEndPoint之后长度算出来就是错的。另外确认文本框不是readonly或disabled这两种状态下选区行为异常。报错三有选中文本时结果偏移。回到第 4 节说的两种写法。你要选区起点就用storedRange.text.length - range.text.length要选区终点就直接取storedRange.text.length。别混用。报错四API 返回 401 或 403。Key 复制时带了空格或者用了别的平台的 Key。重新去 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 复制一次注意别把Bearer后面的空格漏掉。报错五请求超时。老代码片段太长模型处理慢。把timeout_ms调到 120000或者把代码拆成几段分别审查。如果长期做这种批量审查考虑用 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 它的额度模型更适合高频调用。报错六IE8 下selectionStart存在但值不对。这是文档模式问题。检查页面有没有!DOCTYPE html没有的话 IE8 会进怪异模式selectionStart行为不稳定。加上标准 DOCTYPE 后重测。排查顺序建议先确认焦点再确认传参是 DOM 元素最后确认选区起点还是终点。这三步能解决九成问题。6. 把通道和兼容代码一起用起来配置和代码都跑通之后你可以把 TaoToken 的调用嵌进日常流程。比如写一个 Node 脚本读取项目里的 IE 兼容文件逐段发给模型审查把可疑的document.selection用法标出来。脚本里读settings.json的api_base和api_key请求发到 https://taotoken.net/api 返回结果写进报告文件。如果你更习惯在对话里干活直接把光标函数和测试结果贴进模型对话页 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentchatutm_campaignrewrite 让它针对 IE6 的setEndPoint边界情况补测试用例。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面有完整的参数说明和错误码对照遇到 4xx 先查这里。最后留一个实用技巧IE 6/7/8 的TextRange在文本框内容为空时moveToElementText之后text.length是 0减法写法也是 0结果正确不用额外判空。但如果文本框里有换行符IE 会把\r\n算成两个字符而现代浏览器的selectionStart只算一个。跨浏览器统计字数时这个差异要单独处理否则光标位置在含换行的文本里会对不上。
