前端必修:从表单基础到富文本编辑,一文吃透 HTML 表单编程与交互

1 表单基础(acceptCharset / action / elements / enctype / length / method / name / reset / submit / target 等)

概念 :这些是 <form> 的属性或与表单相关的 DOM 属性/方法,用于定义提交目标、编码方式、提交方法以及通过脚本访问表单控件等。

原理

  • action:提交目标 URL(表单数据要发送到哪里)。
  • method:提交方法(GETPOST)。GET 把数据放在查询字符串,POST 放在请求体(适合大数据或文件)。
  • enctype:提交数据的编码类型。常见 application/x-www-form-urlencoded(默认)、multipart/form-data(上传文件时)和 text/plain
  • accept-charset:告知服务器哪种字符集被用于表单数据(通常使用 UTF-8)。
  • target:提交后的显示目标(例如 _self, _blank, iframe 名称)。
  • name:表单的名字(旧时常用,可在 window 上形成全局引用,但现在推荐 id)。
  • elements(DOM 属性):表单控件集合(不是 HTML 属性),可通过 form.elements 访问按索引或按控件 name
  • length(DOM 属性): 表示 form.elements.length(控件数量)。
  • submit / reset:既是按钮类型(<input type="submit">)也是表单方法(form.submit()form.reset())。

对比

  • form.submit()(程序化提交) vs 用户点击提交按钮:form.submit() 跳过 submit 事件的触发与约束验证(注意:requestSubmit() 是触发验证并触发 submit 事件的较新方法)。
  • enctype="multipart/form-data" 必须和 input[type="file"] 一起使用来上传文件;否则二进制文件不能正常发送。

实践(示例)

xml 复制代码
<!-- 示例表单(逐行注释) -->
<form id="demoForm"
      action="/submit"              <!-- 表单要发往的 URL -->
      method="POST"                 <!-- 使用 POST 提交数据 -->
      enctype="multipart/form-data" <!-- 用于上传文件 -->
      accept-charset="UTF-8"        <!-- 告知字符编码 -->
      target="_self"                <!-- 提交后在当前窗口打开结果 -->
      novalidate                    <!-- 临时禁用 HTML5 自动验证(可选) -->
>
  <!-- 文件输入 -->
  <input type="file" name="avatar" />
  <!-- 文本输入 -->
  <input type="text" name="username" />
  <!-- 提交按钮 -->
  <button type="submit">提交</button>
  <!-- 重置按钮 -->
  <button type="reset">重置</button>
</form>

<script>
  // JS:访问表单、elements、length
  const form = document.getElementById('demoForm'); // 获取表单 DOM
  console.log('控件数量(form.elements.length):', form.elements.length); // length 属性
  console.log('通过 name 访问 username:', form.elements['username']); // 按 name 获取控件
  // programmatic submit 注意事项(见下)
  // form.submit(); // 直接提交(不会触发 submit 事件也不会触发验证)
  // 推荐:form.requestSubmit()(如果浏览器支持)会触发验证和 submit 事件
</script>

拓展

  • form.enctypeFormData:在使用 fetch + FormData 时,浏览器会自动设置 Content-Type: multipart/form-data 边界(boundary)。
  • target 可以指向 iframe 名称以在页面内嵌显示结果。

潜在问题

  • form.submit() 跳过 submit 事件与约束验证(可能导致不受控提交)。现代推荐使用 requestSubmit() 或触发按钮的 click()
  • 使用 accept-charset 多数情况下无需手动设置,只要服务端与页面使用 UTF-8 即可。

2 提交表单(submit)

概念 :表单提交可以由用户点击提交按钮触发,也可由脚本触发(form.submit()form.requestSubmit())。提交前浏览器会执行约束验证(默认情况下)。

原理

  • 用户触发(例如 <button type="submit">)会触发 submit 事件,事件处理器 preventDefault() 可阻止默认提交。
  • form.submit():程序化提交,不触发 submit 事件,也不执行约束验证。
  • form.requestSubmit():较新的 API,会模拟用户提交(会执行约束验证并触发 submit 事件),可以传入触发提交的按钮以保留其 formmethod 等属性。

对比

  • form.submit():快速直接,但跳过验证与事件。
  • requestSubmit():更"语义正确",会触发验证与事件(如果浏览器支持);兼容性较新,但主流现代浏览器已支持。

实践(示例并注释)

xml 复制代码
<form id="f1">
  <input name="email" type="email" required />
  <button id="btn" type="submit">送出</button>
</form>

<script>
  const f = document.getElementById('f1');

  // 拦截 submit 事件,做自定义处理:
  f.addEventListener('submit', function (e) {
    e.preventDefault(); // 阻止默认的表单提交(便于做 AJAX)
    // 验证可用 checkValidity() / reportValidity()
    if (!f.checkValidity()) {
      // 报告并显示浏览器内置提示
      f.reportValidity();
      return;
    }
    // 继续:可以使用 fetch 提交或者 f.submit() 提交
    // 例如将数据收集后用 fetch 发送:
    const fd = new FormData(f);
    // fetch('/submit', { method: 'POST', body: fd }) ...
    console.log('准备通过 fetch 提交', [...fd.entries()]);
  });

  // 程序化提交示例(推荐使用 requestSubmit)
  document.getElementById('btn').addEventListener('click', () => {
    // f.requestSubmit(); // 如果浏览器支持,会触发验证与submit事件
    // 兼容写法:若不支持 requestSubmit,可创建隐藏按钮并 click()
    if (typeof f.requestSubmit === 'function') {
      f.requestSubmit();
    } else {
      // fallback:创建临时 submit 按钮并点击
      const tmp = document.createElement('button');
      tmp.type = 'submit';
      tmp.style.display = 'none';
      f.appendChild(tmp);
      tmp.click();
      f.removeChild(tmp);
    }
  });
</script>

拓展

  • formdata 事件(FormDataEvent)可在提交前操作 FormData(某些场景)。
  • 使用 fetch + FormData 完全替代表单提交,便于 SPA 异步提交。

潜在问题

  • 忽略验证(调用 form.submit())会让无效数据提交。
  • 老旧浏览器可能不支持 requestSubmit(),需 fallback。

3 重置表单(reset)

概念form.reset() 将表单控件的当前值恢复到初始(页面加载或最后一次设置默认值)状态。<button type="reset"> 触发同样的行为。

原理

  • 每个控件有 .defaultValue / .defaultChecked 属性,reset() 会把 value/checked 恢复为 .defaultValue/.defaultChecked

实践(示例并注释)

xml 复制代码
<form id="fReset">
  <input name="name" value="初始名" />
  <input type="checkbox" name="agree" checked />
  <button type="reset">重置</button>
</form>

<script>
  const fr = document.getElementById('fReset');
  // 程序化重置:
  // fr.reset(); // 会把控件 value/checked 恢复为其 default 值
  // 示例:改变值后再 reset 可看到恢复
  fr.elements['name'].value = '改变后的值';
  console.log('改变后的值:', fr.elements['name'].value); // 改变后
  // fr.reset(); // 恢复到 "初始名"
</script>

拓展

  • 可在 reset 事件上拦截并做提示或阻止(例如 e.preventDefault()),然后进行自定义行为。

潜在问题

  • reset() 不会清除 defaultValue,只能恢复到 default。如果需要清空表单,应手动设置 value = ''

4 表单字段(使用 form.elements 访问 name 对应的值)

概念form.elements 返回 HTMLFormControlsCollection,按索引、按 nameid 访问。也可使用 new FormData(form) 快速读取所有可提交字段。

原理

  • form.elements['foo'] 若有多个同名控件会返回集合(HTMLCollection-like),单个 name 返回元素对象。
  • FormData 会收集控件的值(包括文件)并可迭代。

实践(示例并注释)

xml 复制代码
<form id="fElements">
  <input name="username" value="alice" />
  <input name="hobby" value="reading" />
  <input name="hobby" value="coding" />
</form>

<script>
  const f = document.getElementById('fElements');

  // 通过 form.elements 访问单个控件
  const usernameInput = f.elements['username']; // 直接得到 input 元素
  console.log('username value:', usernameInput.value); // alice

  // 同名控件 hobby:会返回 HTMLCollection(多个选项)
  const hobbies = f.elements['hobby'];
  console.log('hobby 类型:', Object.prototype.toString.call(hobbies)); // NodeList 或 HTMLCollection
  // 如果要把同名输入的值组合为数组:
  const hobbyValues = Array.from(f.elements['hobby']).map(i => i.value);
  console.log('hobby values:', hobbyValues);

  // 使用 FormData 读取当前可提交的数据(推荐)
  const fd = new FormData(f);
  for (const [k, v] of fd.entries()) {
    console.log(k, v);
  }
</script>

拓展

  • FormData 可用于 fetchfetch('/api', { method: 'POST', body: new FormData(f) })
  • Object.fromEntries(new FormData(f)) 可快速构造纯 JS 对象(注意多值同名字段会只保留最后一个值,或需特殊处理)。

潜在问题

  • 使用 form.elements[name] 时如果 name 与 window 全局变量冲突会出问题;尽量使用 id + querySelectorFormData 来避免歧义。

5 表单验证(使用 form.checkValidity()

概念 :HTML5 约束验证 API(Constraint Validation API)允许以声明式(requiredpatterntype="email" 等)或编程方式(checkValidity()setCustomValidity()reportValidity())进行验证。

原理

  • element.checkValidity():返回布尔值,表示该控件是否满足约束;若不满足,会触发该控件的 invalid 事件(可监听)。
  • form.checkValidity():遍历表单控件,若全部通过返回 true,否则返回 false(并会触发控件的 invalid 事件)。
  • element.setCustomValidity(msg):设置自定义错误信息,非空字符串使控件无效(checkValidity() 返回 false),空字符串恢复有效。
  • reportValidity():会显示浏览器的内置提示并返回验证结果(等同 checkValidity() + 展示消息)。

注:浏览器行为细节随现代浏览器发展略有差异,但上述 API 是规范行为。


5.1 表单中的公共属性(disabled form name readOnly tabIndex type value

概念与实践(举例并注释):

xml 复制代码
<input id="ex" name="ex" type="text" value="初始" readonly tabindex="5" />
<!-- 属性说明:
     - disabled: 控件禁用(不可交互,提交时不会包含在表单数据中)
     - form: 指定所属表单的 id(用于把控件放在表单外仍归属某个表单)
     - name: 提交 key
     - readOnly: 只读(可聚焦、不可编辑)
     - tabIndex: 控制 tab 键聚焦顺序(-1 表示不可通过 tab 获得)
     - type/value: 控件类型与当前值 -->

拓展

  • disabledreadOnly 的区别:disabled 通常不可聚焦且不会提交;readOnly 可聚焦、可复制但不可编辑,会提交。

5.2 表单字段的公共方法(focus blur

概念element.focus() 将焦点移动到控件上;element.blur() 使控件失去焦点。常用于表单自动聚焦、错误后聚焦等。

实践

xml 复制代码
<input id="email" />
<script>
  const e = document.getElementById('email');
  e.focus(); // 聚焦到 email 输入框
  // 模拟延迟失焦(示例)
  setTimeout(() => e.blur(), 2000); // 2 秒后失去焦点
</script>

潜在问题

  • 在不可见或 display:none 元素上调用 focus() 可能无效或抛异常;确保元素可见并可聚焦。

5.3 表单字段的公共事件(change input invalid reset select submit blur

概念

  • input:实时输入时触发(任何字符变更)。
  • change:控件值"改变并失焦后"触发(对于 select 则是选中即触发)。
  • invalid:控件验证失败时触发(可拦截)。
  • reset(表单):表单被 reset 时触发。
  • select:当文本被选中(input/textarea)触发。
  • submit:表单提交时触发(用户行为或 requestSubmit() 会触发;form.submit() 不触发)。
  • blur:失去焦点时触发。

实践(示例)

ini 复制代码
<form id="evtForm">
  <input id="i1" required />
  <textarea id="ta"></textarea>
  <button type="submit">发送</button>
  <button type="reset">清除</button>
</form>

<script>
  const f = document.getElementById('evtForm');
  const i1 = document.getElementById('i1');
  const ta = document.getElementById('ta');

  i1.addEventListener('input', e => {
    console.log('输入中:', e.target.value); // 实时输入
  });

  i1.addEventListener('change', e => {
    console.log('change: 值已改变且失焦', e.target.value);
  });

  i1.addEventListener('invalid', e => {
    e.preventDefault(); // 阻止默认提示,改为自定义提示
    console.warn('输入无效:', e.target.validationMessage);
  });

  ta.addEventListener('select', e => {
    // 当用户选中文本时触发(仅 input/textarea)
    const start = e.target.selectionStart;
    const end = e.target.selectionEnd;
    console.log('选中文本:', e.target.value.slice(start, end));
  });

  f.addEventListener('reset', e => {
    console.log('表单已重置');
  });

  f.addEventListener('submit', e => {
    e.preventDefault();
    console.log('submit 被捕获,可做异步提交');
  });
</script>

潜在问题

  • invalid 事件会在控件第一次无效时触发并可被阻止;如果想统一处理所有错误,需监听 submit 并用 checkValidity()

6 文本框编程(type="text"

概念<input type="text"> 是最常见的单行文本输入控件,支持 maxlengthplaceholderpatternautocomplete 等属性。

实践

ini 复制代码
<input id="t" type="text" name="nick" placeholder="请输入昵称" maxlength="20" />
<script>
  const t = document.getElementById('t');
  console.log('当前值:', t.value);
  // 设置默认值(也会同时改变 defaultValue)
  t.defaultValue = '默认昵称';
</script>

拓展

  • inputmode 可以建议软键盘布局(如 numeric),对移动端友好。
  • autocomplete 控制是否允许浏览器自动填充。

7 选择文本(event.target.select()

概念

  • <input><textarea> 中,可使用 element.select() 选中全部文本。
  • select 事件在选中文本时触发(input/textarea)。

实践(示例)

typescript 复制代码
<input id="selDemo" type="text" value="选我试试" />
<button id="doSelect">全选</button>

<script>
  const inp = document.getElementById('selDemo');
  document.getElementById('doSelect').addEventListener('click', () => {
    inp.select(); // 选中 input 中所有文本
  });

  inp.addEventListener('select', e => {
    // select 事件:可拿到 selectionStart / selectionEnd
    const start = e.target.selectionStart;
    const end = e.target.selectionEnd;
    console.log('选中位置:', start, end);
    console.log('选中文本:', e.target.value.slice(start, end));
  });
</script>

7.2 取得选中文本(document.selection.createRange().text 与 event / selection API)

对比

  • document.selection.createRange().text:IE(<=8)专用旧 API,不再推荐。

  • 现代浏览器:

    • 对于 input/textarea:使用 selectionStart/selectionEnd 并截取 value.slice(start,end)
    • 对于 contenteditable 或普通页面文本:使用 window.getSelection() + Selection.toString()

实践(示例)

ini 复制代码
// 输入框选中文本:
const el = document.querySelector('input');
const selected = el.value.slice(el.selectionStart, el.selectionEnd);

// contenteditable 或文档文本:
const sel = window.getSelection();
const text = sel.toString(); // 选中文本

7.3 部分选中文本(setSelectionRange(start, end)

实践

ini 复制代码
const el = document.querySelector('input');
el.setSelectionRange(2, 5); // 将光标/选择设为从索引 2 到 5(仅对可选择的 input/textarea)
el.focus(); // 通常需要先 focus 才能看到选择效果

潜在问题

  • 对于某些类型(如 type="number"),部分浏览器可能不支持 selectionStart/EndsetSelectionRange,最佳实践是用 type="text" 或在需要时临时切换。

8 输入过滤(概念与使用场景)

概念 :输入过滤指在用户输入时限制或清洗不合规字符(例如只允许数字、禁止表情、手机号码格式化等)。可以在 keydown/keypress 阻止输入,也可以在 input 事件后修正值,或利用 pattern 做宣告式约束。

对比

  • 事件阻止(keydown/keypress + preventDefault()):可在输入发生前阻断,但键盘以外输入(粘贴、拖拽、输入法)仍需处理。
  • input 事件后替换值:更稳健,能覆盖粘贴/输入法场景,但用户会短暂看到不合规字符(可即时替换)。
  • pattern + title:仅用于验证,不会阻止输入,但会在提交时提示。

8.1 屏蔽字符(示例使用事件阻止字符输入)

实践(示例:仅允许数字)

typescript 复制代码
<input id="numOnly" type="text" placeholder="只允许数字" />
<script>
  const num = document.getElementById('numOnly');

  // 推荐:在 input 事件中清理(覆盖所有输入方式)
  num.addEventListener('input', (e) => {
    // 只保留数字:用正则清理
    const cleaned = e.target.value.replace(/\D+/g, '');
    if (cleaned !== e.target.value) {
      // 保持光标位置需要更复杂处理;这里简单替换
      e.target.value = cleaned;
    }
  });

  // 可选:在 keydown 阻止非数字键(兼顾用户体验)
  num.addEventListener('keydown', e => {
    // 允许控制键(Backspace, Delete, Arrow keys)
    if (e.ctrlKey || e.metaKey || e.altKey) return;
    const allowed = ['Backspace','ArrowLeft','ArrowRight','Delete','Tab'];
    if (allowed.includes(e.key)) return;
    if (!/^\d$/.test(e.key)) {
      e.preventDefault(); // 阻止非数字按键
    }
  });
</script>

潜在问题

  • 输入法(IME)在 composition 阶段会触发多个事件,阻断键盘事件可能破坏中文输入体验。对中文/日文场景需特别处理(监听 compositionstart/compositionend)。

8.2 处理剪贴板(API、示例、浏览器兼容)

概念

  • 处理剪贴板可分为"响应粘贴事件处理粘贴内容"(paste 事件 + event.clipboardData)与"主动读取/写入剪贴板"(navigator.clipboard.readText() / writeText(),需要权限/HTTPS)。

实践(粘贴事件处理示例并注释)

ini 复制代码
<textarea id="pasteArea" placeholder="粘贴文本,会自动移除脚本标签"></textarea>
<script>
  const ta = document.getElementById('pasteArea');

  ta.addEventListener('paste', (e) => {
    e.preventDefault(); // 阻止默认粘贴行为(我们将插入清理后的内容)
    // event.clipboardData 在 paste 事件中可用
    const text = (e.clipboardData || window.clipboardData).getData('text/plain');
    // 简单清理示例:移除 <script> 标签,防止 XSS(生产中应更严谨使用白名单 sanitizer)
    const sanitized = text.replace(/<script[\s\S]*?>[\s\S]*?</script>/gi, '');
    // 将清理后的文本插入到光标位置(现代方法)
    const start = ta.selectionStart;
    const end = ta.selectionEnd;
    const newVal = ta.value.slice(0, start) + sanitized + ta.value.slice(end);
    ta.value = newVal;
    // 将光标移到插入后的位置
    const cursor = start + sanitized.length;
    ta.setSelectionRange(cursor, cursor);
  });

  // 使用 navigator.clipboard(主动读取/写入)示例(需 HTTPS 和用户授权)
  async function copyTextToClipboard(text) {
    if (navigator.clipboard && navigator.clipboard.writeText) {
      await navigator.clipboard.writeText(text); // 写入剪贴板(异步)
    } else {
      // 回退:使用 document.execCommand('copy') + 临时 textarea
      const tmp = document.createElement('textarea');
      tmp.value = text;
      document.body.appendChild(tmp);
      tmp.select();
      try { document.execCommand('copy'); } catch (err) { console.warn('copy fallback failed', err); }
      document.body.removeChild(tmp);
    }
  }
</script>

兼容性说明

  • event.clipboardData 在大多数现代桌面浏览器的 paste 事件中可用;移动浏览器行为可能差异。
  • navigator.clipboard(异步 API)需要安全上下文(HTTPS)且浏览器要求交互权限;并非所有浏览器或场景都允许主动读写(读取通常受限)。
  • document.execCommand('copy') 是老方法,仍作为回退,但浏览器对 execCommand('paste') 通常限制严格。

潜在问题

  • 剪贴板内容可能包含恶意 HTML/脚本,必须在插入 DOM 前 sanitize(使用 DOMPurify 等库更安全)。
  • 读取剪贴板涉及隐私与权限问题,使用时应明确提示用户。

9 自动切换(使用 focus 自动切换用户输入的 input,常见于 OTP)

概念 :用于电话号码/验证码分段输入,用户输入满位后自动 focus() 到下一个输入框;删除时返回上一个。

实践(示例并注释)

xml 复制代码
<!-- OTP 示例:4 位数字 -->
<div id="otp">
  <input inputmode="numeric" maxlength="1" class="otp" />
  <input inputmode="numeric" maxlength="1" class="otp" />
  <input inputmode="numeric" maxlength="1" class="otp" />
  <input inputmode="numeric" maxlength="1" class="otp" />
</div>

<script>
  const inputs = document.querySelectorAll('#otp .otp');
  inputs.forEach((el, idx) => {
    el.addEventListener('input', (e) => {
      // 每次输入后,如果输入了字符,自动跳到下一个
      if (e.target.value.length >= 1) {
        const next = inputs[idx + 1];
        if (next) next.focus();
      }
    });
    el.addEventListener('keydown', (e) => {
      // 处理退格键:如果当前为空并按 Backspace,跳回上一个并清空
      if (e.key === 'Backspace' && e.target.value === '') {
        const prev = inputs[idx - 1];
        if (prev) {
          prev.focus();
          prev.value = '';
          e.preventDefault();
        }
      }
    });
  });
</script>

潜在问题

  • 输入法(IME)或粘贴多字符时需处理(例如粘贴 "1234" 到第一个输入,应分散填充四个输入)。

10 HTML5 约束验证 API(详细)

概念 :HTML5 提供了约束验证属性(required, min, max, pattern, type=email 等)和 JS API(checkValidity()reportValidity()setCustomValidity())来检查输入是否符合约束。


10.1 必填字段(required

实践

ini 复制代码
<input required name="email" type="email" />

form.checkValidity() 在必填为空时返回 falsereportValidity() 会显示浏览器提示。


10.2 更多输入类型(email url

概念

  • type="email":浏览器会进行格式检查(含 @ 等),但不保证是可达地址;可配合 pattern 做更严格检查。
  • type="url":检查是否符合 URL 格式。

实践

ini 复制代码
<input type="email" name="e" />
<input type="url" name="site" />

10.3 数值范围(min max stepstepUp/stepDown

概念

  • input[type="number"] 支持 minmaxstep
  • DOM 方法 stepUp(n)stepDown(n) 可调整值(若超出范围,会根据浏览器行为进行限制)。

实践

typescript 复制代码
<input id="num" type="number" min="0" max="10" step="1" value="5" />
<button id="up">上一步</button>
<button id="down">下一步</button>

<script>
  const num = document.getElementById('num');
  document.getElementById('up').addEventListener('click', () => num.stepUp()); // +1
  document.getElementById('down').addEventListener('click', () => num.stepDown()); // -1
</script>

10.4 输入模式(pattern

概念pattern 属性定义一个正则表达式用于匹配控件值(仅在提交/验证时生效)。

实践

ini 复制代码
<input pattern="^\d{4}$" title="请输入 4 位数字" />

10.5 检测有效性(checkValidity()

实践

javascript 复制代码
if (document.getElementById('myForm').checkValidity()) {
  // 有效
} else {
  // 无效;可用 reportValidity() 显示浏览器提示
  document.getElementById('myForm').reportValidity();
}

10.6 禁用验证(novalidate

概念 :在 <form novalidate> 上添加 novalidate 将禁用浏览器的约束验证(适用于完全用 JS 自定义验证的场景)。

潜在问题

  • 若禁用验证,开发者必须自己确保所有必要的验证(含服务端验证)都到位,避免安全问题。

11 选择框编程(select 的属性与方法)

概念<select> 元素有丰富的 API:options(集合)、selectedIndexmultiplesizeadd()/remove()selectedOptionsselected(option 属性)等。

实践(示例并注释)

xml 复制代码
<select id="s" size="4">
  <option value="a">A</option>
  <option value="b">B</option>
</select>

<script>
  const s = document.getElementById('s');

  // 添加选项:new Option(text, value)
  const opt = new Option('C', 'c'); // 创建 Option
  s.add(opt); // append to select

  // 移除选项:按索引
  s.remove(1); // 移除第2项

  // 读取选中项
  console.log('selectedIndex:', s.selectedIndex);
  console.log('selected value:', s.value); // 如果 multiple,value 为第一个被选的值

  // multiple 情况,读取所有被选项
  if (s.multiple) {
    const vals = Array.from(s.selectedOptions).map(o => o.value);
    console.log(vals);
  }

  // insertBefore 用于在指定位置插入(可用来重排)
  const opt2 = new Option('D','d');
  s.insertBefore(opt2, s.options[0]); // 在最前面插入
</script>

潜在问题

  • s.value 在 multiple 的 select 中只返回第一个被选中的值;需要用 selectedOptions 获取所有。

12 选项处理(使用 options 属性)

概念select.options 是一个 HTMLCollection(类数组),可以用索引读写,或用 length 管理。

实践

ini 复制代码
const sel = document.querySelector('select');
for (let i = 0; i < sel.options.length; i++) {
  console.log(sel.options[i].value, sel.options[i].text);
}
// 添加
sel.options[sel.options.length] = new Option('text','val');
// 或 sel.add(new Option('t','v'), index);

13 添加选项(通过 new Option 并使用 DOM 句柄或 appendChild

实践

csharp 复制代码
const opt = new Option('显示文本', 'value');
// 使用 select.add()
select.add(opt, 1); // 在索引 1 位置插入(部分旧浏览器第二个参数要求是引用节点)

14 移除选项(select.remove(index)box.removeChild

实践

csharp 复制代码
select.remove(2); // 移除索引 2 的选项
// 或:select.removeChild(select.options[2]);

15 移动和重排选项(insertBefore

实践(示例:把选中项移动到顶部):

scss 复制代码
function moveSelectedToTop(sel) {
  const opt = sel.options[sel.selectedIndex];
  if (opt) {
    sel.insertBefore(opt, sel.options[0]); // 将该选项插至首位
  }
}

16 富文本的编辑(contenteditable、execCommand、Selection、提交)

概念contenteditable 允许元素在浏览器中变成可编辑区域;document.execCommand(已被弃用但仍广泛支持)曾用于对富文本进行格式化(加粗、插入链接等)。现在推荐使用 Selection/Range API 或成熟的富文本编辑器库(Quill、CKEditor、ProseMirror、TipTap 等)。

原理

  • contenteditable="true" 将元素标记为可编辑,用户可输入并产生 DOM(<b>, <i>, <a> 等)。
  • document.execCommand(command, false, value):执行内置命令;命令列表历史上包含 bolditalicinsertHTMLcreateLink 等,但规范标注为"废弃",行为各浏览器略有不同。

对比

  • execCommand:简单、内置但已废弃,不稳定;适合简单 demo。
  • 使用 Selection + Range 操作 DOM:更现代、可控,但实现复杂。
  • 使用第三方库:推荐方式,处理了大量兼容性与功能(工具栏、撤销/重做、粘贴清理、协作等)。

16.1 使用 contenteditable(示例)

css 复制代码
<div id="editor" contenteditable="true" style="border:1px solid #ccc; padding:8px; min-height:100px;">
  这里是可编辑内容
</div>

16.2 与富文本交互(execCommand 命令列表与示例)

常用命令(非穷尽,且浏览器支持有限)

  • bold, italic, underline, strikeThrough
  • insertHTML(插入 HTML)
  • createLink(创建链接,value 为 URL)
  • unlink
  • insertOrderedList, insertUnorderedList
  • justifyLeft, justifyCenter, justifyRight, justifyFull
  • foreColor, backColor(设置颜色)
  • fontName, fontSize
  • removeFormat

实践(示例并注释)

xml 复制代码
<div>
  <button data-cmd="bold">B</button>
  <button data-cmd="italic">I</button>
  <button data-cmd="createLink">Link</button>
  <button id="getHTML">获取 HTML</button>
</div>
<div id="ed" contenteditable="true">可编辑文本</div>

<script>
  const ed = document.getElementById('ed');

  document.querySelectorAll('button[data-cmd]').forEach(btn => {
    btn.addEventListener('click', () => {
      const cmd = btn.getAttribute('data-cmd');
      if (cmd === 'createLink') {
        const url = prompt('输入链接 URL');
        if (url) document.execCommand('createLink', false, url); // 插入链接(已废弃 API)
      } else {
        document.execCommand(cmd, false, null); // 执行命令,例如 bold/italic
      }
    });
  });

  document.getElementById('getHTML').addEventListener('click', () => {
    // 获取富文本内容(HTML)
    console.log(ed.innerHTML);
  });
</script>

注意execCommand 已被标为过时(deprecated),但多数浏览器仍支持。生产环境建议使用现代编辑库或基于 Selection/Range 的自实现。


16.3 富文本的选择(getSelection()

实践

ini 复制代码
const sel = window.getSelection();
if (sel.rangeCount > 0) {
  const range = sel.getRangeAt(0); // 获取选区
  console.log('选区文本:', sel.toString());
  // range 可用于插入节点、替换、删除等复杂操作
}

16.4 通过表单提交富文本(获取可编辑区域内容并交给表单提交)

实践(示例并注释)

xml 复制代码
<form id="richForm" action="/save" method="POST">
  <!-- 隐藏域用于提交编辑器内容 -->
  <input type="hidden" name="content" id="hiddenContent" />
  <div id="rich" contenteditable="true">初始富文本</div>
  <button type="submit">提交</button>
</form>

<script>
  const form = document.getElementById('richForm');
  const hidden = document.getElementById('hiddenContent');
  const rich = document.getElementById('rich');

  form.addEventListener('submit', (e) => {
    // 在提交前把富文本的 HTML 放入隐藏 input
    hidden.value = rich.innerHTML; // 逐行注释:将 HTML 字符串传给后端,后端需 sanitize
    // 如果使用 AJAX/fetch,则可直接发送 rich.innerHTML
    // 注意:必须在服务器端进行 XSS 过滤和清理
  });
</script>

安全性

  • 富文本内容含 HTML,必须在服务器端进行严格消毒(sanitize)以防 XSS。客户端清理(如 DOMPurify)可作为辅助,但不能替代服务端过滤。

拓展/推荐

  • 推荐使用成熟编辑器(Quill、TipTap、CKEditor 5、ProseMirror)来处理复杂功能(撤销/重做、图片上传、插件等)。

潜在问题

  • document.execCommand 的未来不确定,不建议构建复杂编辑器在其上长期维护。
相关推荐
gnip1 小时前
链式调用和延迟执行
前端·javascript
SoaringHeart2 小时前
Flutter组件封装:页面点击事件拦截
前端·flutter
杨天天.2 小时前
小程序原生实现音频播放器,下一首上一首切换,拖动进度条等功能
前端·javascript·小程序·音视频
Dragon Wu2 小时前
React state在setInterval里未获取最新值的问题
前端·javascript·react.js·前端框架
Jinuss2 小时前
Vue3源码reactivity响应式篇之watch实现
前端·vue3
YU大宗师2 小时前
React面试题
前端·javascript·react.js
木兮xg2 小时前
react基础篇
前端·react.js·前端框架
ssshooter2 小时前
你知道怎么用 pnpm 临时给某个库打补丁吗?
前端·面试·npm
IT利刃出鞘3 小时前
HTML--最简的二级菜单页面
前端·html
yume_sibai3 小时前
HTML HTML基础(4)
前端·html