在浏览器中,表单控件里的文本也能与普通文本一起被复制
请看下面的HTML片段:
foo <input type="text" value="bar">
<select>
<option>baz</option>
</select> qux
你会看到渲染出来的是“foo bar baz qux”。不过,至少在Firefox中,如果你选中整段文本并复制粘贴到其他程序,控件中的文本会被跳过,因此粘贴出来的只有“foo qux”。
那么,怎样才能在不使用JavaScript从头构造剪贴板内容的前提下,把整段字符串“foo bar baz qux”粘贴进去呢?
解决方案
(编辑:因为Kaiido修复了在Firefox中将“隐藏”文本内容节点放在元素内部以便复制的问题(通过属性或真实文本子节点),现在甚至支持 <select>,那个答案也许会是最好的选项,尽管它看起来有些玄。)
不是一个“漂亮”的做法,但相当务实,而且相当直观:
将值在输入框旁边进行镜像显示
与其去折腾copy事件、在JS中从头构造整份剪贴板内容,不如直接让值在相应元素上保持镜像,以便复制时取用——在视觉上隐藏,同时对屏幕阅读器也隐藏。
一个尽量简单的最小可行演示(POC),没有做任何额外的特殊处理;使用对整份文档的捕获式监听来监视 input 事件,并有一个“onload”循环,用于从一开始就设置HTML中的内容。镜像的数值在这里仅为了清晰地显示而被 显示:
function mirrorValueForCopy(event) {
const tgt = event.target;
let prev = tgt.previousElementSibling;
if (prev && prev.hasAttribute('data-mirror')) {
prev.textContent = tgt.value;
} else {
tgt.insertAdjacentHTML(
'beforebegin',
`<mark data-mirror aria-hidden="true">${tgt.value}</mark>`
);
}
}
document.documentElement.addEventListener('input', mirrorValueForCopy, true);
document.querySelectorAll('input,select').forEach(i => mirrorValueForCopy({
target: i
}))
[data-mirror] {
font-size: .7rem;
/* Use 0 for real hiding, or, you know, clip, position, whatever. */
}
foo
<input value="bar" id="a">
<select>
<option>baz</option>
<option>gazonk</option>
</select>
qux
实际应用中的注意事项与细节
- 显然,不适用于依赖于 确切的DOM顺序、同级节点索引等的HTML/CSS结构。
- 为简洁起见,省略了避免在Chromium中产生重复剪贴板内容的任何尝试。
- 为简洁起见,省略了对尚未交互过的新追加输入元素设置镜像的MutationObserver(变更观察器)。
- 也没有对Firefox能原生从中复制值的输入发射器设置提前返回的过滤逻辑(如果存在的话)。
附录:把“真实文本”作为伪标签叠加显示
这种机制在某种程度上提出了一个问题——把这些重复项做成看起来像实际输入内容的样子?我做了一些测试,结果在一定程度上可行,但可能带来比它解决的问题更多的新问题。POC:
function mirrorValueForCopy(event) {
const tgt = event.target;
if (tgt.matches('[type="checkbox"],[type="radio"]')) return;
let prev = tgt.previousElementSibling;
if (prev && prev.hasAttribute('data-mirror')) {
prev.textContent = tgt.value;
} else {
tgt.insertAdjacentHTML(
'beforebegin',
`<mark data-mirror aria-hidden="true">${tgt.value}</mark>`
);
}
}
document.documentElement.addEventListener('input', mirrorValueForCopy, true);
document.querySelectorAll('input:not([type="checkbox"],[type="radio"]),select').forEach(i => mirrorValueForCopy({
target: i
}))
[data-mirror] {
position: absolute;
&:has(+ :focus) {
z-index: -1;
}
pointer-events: none;
white-space: pre;
}
<label>
<input type="checkbox" id="nl2space" checked>
single line clipboard
</label>
<p oncopy="
if(!nl2space.checked){return};
event.clipboardData.setData(
'text/plain',
document.getSelection().toString().replaceAll(/\r\n|\r|\n/g,' ')
);
event.preventDefault();
">
foo
<input value="bar">
<select>
<option>baz</option>
<option>gazonk</option>
</select>
qux
</p>
<textarea placeholder="try here"></textarea>.
存在的问题和注意事项:
- 禁用了pointer-events,使点击能够作用于实际的input/select。
- 虚拟标签的浮动可能作为实际标签使用,但会丢失位置信息,并且无法打开select。
- Firefox在选中的区域包含定位内容时往往会添加换行符。这就是为什么那里有oncopy的清理逻辑。
- 可以说,保留oncopy处理程序已经非常接近我们想避免的“重新构建剪贴板内容”的做法,但至少没有涉及TreeWalker(树遍历器)相关的实现。
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。