英文:
Duplicate browser tab ignores form elements current values
问题
Issue
当你在浏览器中复制一个标签页时,form
元素的当前值被忽略。在Windows 11上的最新版本的Chrome、Firefox和Edge中进行了测试。
Sample code & demo
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
var textarea = document.querySelector('textarea');
var p = document.querySelector('p');
var range = document.querySelector('input[type="range"]');
var output = document.querySelector('output');
var checkbox = document.querySelector('input[type="checkbox"]');
var span = document.querySelector('span');
var theme = document.querySelector('select');
function write() {
p.textContent = textarea.value;
output.value = range.value;
span.textContent = checkbox.checked;
document.body.className = theme.value;
}
textarea.addEventListener('input', write);
range.addEventListener('input', write);
checkbox.addEventListener('change', write);
theme.addEventListener('change', write);
write();
<!-- language: lang-css -->
body {
display: grid;
grid-template-columns: repeat(2, max-content);
gap: 1em 0.5em;
}
body.Dark {
color: white;
background: black;
}
<!-- language: lang-html -->
<textarea>Hello, world!</textarea>
<p></p>
<input type="range">
<output></output>
<input type="checkbox">
<span></span>
<select>
<option>Light</option>
<option>Dark</option>
</select>
<!-- end snippet -->
Steps to reproduce the issue
- 打开演示页面或创建您自己的页面。
- 更改
textarea
、input
和select
的默认值。 - 复制标签页。
p
、output
和span
元素不显示预期的文本内容,主题仍然是亮色。
Question
- 为什么会发生这种情况?
- 解决方案是什么?
英文:
Issue
When you duplicate a tab in your browser, the current values of form
elements are ignored. Tested in the latest versions of Chrome, Firefox and Edge on a Windows 11 computer.
Sample code & demo
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
var textarea = document.querySelector('textarea');
var p = document.querySelector('p');
var range = document.querySelector('input[type="range"]');
var output = document.querySelector('output');
var checkbox = document.querySelector('input[type="checkbox"]');
var span = document.querySelector('span');
var theme = document.querySelector('select');
function write() {
p.textContent = textarea.value;
output.value = range.value;
span.textContent = checkbox.checked;
document.body.className = theme.value;
}
textarea.addEventListener('input', write);
range.addEventListener('input', write);
checkbox.addEventListener('change', write);
theme.addEventListener('change', write);
write();
<!-- language: lang-css -->
body {
display: grid;
grid-template-columns: repeat(2, max-content);
gap: 1em 0.5em;
}
body.Dark {
color: white;
background: black;
}
<!-- language: lang-html -->
<textarea>Hello, world!</textarea>
<p></p>
<input type="range">
<output></output>
<input type="checkbox">
<span></span>
<select>
<option>Light</option>
<option>Dark</option>
</select>
<!-- end snippet -->
Steps to reproduce the issue
- Open the demo page or create your own.
- Change the
textarea
,input
s, andselect
default values. - Duplicate the tab.
- The
p
,output
andspan
elements don't show the expected text content and the theme is still light.
Question
- Why does it happen?
- What's the solution?
答案1
得分: 2
Sure, here are the translated parts:
-
Why does it happen?
因为浏览器选项卡中的表单不会存储当前值。 -
What's the solution?
有两种解决方案:
1. 使用查询参数
查询参数将当前值存储为 URL 搜索参数,因此可以通过 new URL(document.location).searchParams
在复制浏览器选项卡时访问以重新填充数据。
代码:https://playcode.io/queryparams
演示:https://queryparams.playcode.io
2. 使用会话存储
会话存储将当前值存储为 会话数据,因此可以通过 .getItem
方法在复制浏览器选项卡时访问以重新填充数据。
代码:https://playcode.io/sessionstorage
演示:https://sessionstorage.playcode.io
英文:
> 1. Why does it happen?
Because the form in the browser tab doesn't store current value.
> 2. What's the solution?
Two solutions for solving that:
1. Using Query Params
Query params will store the current value as URL Search Params so it can be accessed to refill data when duplicating the browser tab by new URL(document.location).searchParams
.
Code: https://playcode.io/queryparams
Demo: https://queryparams.playcode.io
<!-- begin snippet: js hide: true console: true babel: false -->
<!-- language: lang-html -->
<!DOCTYPE html>
<html>
<head>
<title>Duplicate browser tab ignores form elements current values</title>
<style>
body {
display: grid;
grid-template-columns: repeat(2, max-content);
gap: 1em 0.5em;
}
body.Dark {
color: white;
background: black;
}
</style>
</head>
<body>
<textarea>Hello, world!</textarea>
<p></p>
<input type="range" />
<output></output>
<input type="checkbox" />
<span></span>
<select>
<option>Light</option>
<option>Dark</option>
</select>
<script>
var textarea = document.querySelector("textarea");
var p = document.querySelector("p");
var range = document.querySelector('input[type="range"]');
var output = document.querySelector("output");
var checkbox = document.querySelector('input[type="checkbox"]');
var span = document.querySelector("span");
var theme = document.querySelector("select");
let currentParams = new URL(document.location).searchParams;
function createQueryParams() {
let newParams = new URLSearchParams({
textarea: textarea.value,
range: range.value,
checkbox: checkbox.checked,
theme: theme.value,
});
window.history.pushState("", "", `${location.pathname}?${newParams}`);
}
function applyQueryParams() {
textarea.value = currentParams.get("textarea") !== undefined ? currentParams.get("textarea") : textarea.value;
range.value = currentParams.get("range") ? currentParams.get("range") : range.value;
checkbox.checked = currentParams.get("checkbox") ? (currentParams.get("checkbox") == 'true') : checkbox.checked;
theme.value = currentParams.get("theme") ? currentParams.get("theme") : theme.value;
write();
}
function write() {
textarea.innerHTML = textarea.value;
p.textContent = textarea.value;
output.textContent = range.value;
span.textContent = checkbox.checked;
document.body.className = theme.value;
createQueryParams();
}
textarea.addEventListener("input", write);
range.addEventListener("input", write);
checkbox.addEventListener("change", write);
theme.addEventListener("change", write);
applyQueryParams();
</script>
</body>
</html>
<!-- end snippet -->
2. Using Session Storage
Session storage will store the current value as session data so it can be accessed to refill data when duplicating the browser tab by .getItem
method.
Code: https://playcode.io/sessionstorage
Demo: https://sessionstorage.playcode.io
<!-- begin snippet: js hide: true console: true babel: false -->
<!-- language: lang-html -->
<!DOCTYPE html>
<html>
<head>
<title>Duplicate browser tab ignores form elements current values</title>
<style>
body {
display: grid;
grid-template-columns: repeat(2, max-content);
gap: 1em 0.5em;
}
body.Dark {
color: white;
background: black;
}
</style>
</head>
<body>
<textarea>Hello, world!</textarea>
<p></p>
<input type="range" />
<output></output>
<input type="checkbox" />
<span></span>
<select>
<option>Light</option>
<option>Dark</option>
</select>
<script>
var textarea = document.querySelector("textarea");
var p = document.querySelector("p");
var range = document.querySelector('input[type="range"]');
var output = document.querySelector("output");
var checkbox = document.querySelector('input[type="checkbox"]');
var span = document.querySelector("span");
var theme = document.querySelector("select");
let currentSession = JSON.parse(sessionStorage.getItem('data')) || {};
function createSessionStorage() {
let newSession = {
textarea: textarea.value,
range: range.value,
checkbox: checkbox.checked,
theme: theme.value,
};
sessionStorage.setItem('data', JSON.stringify(newSession));
}
function applySessionStorage() {
textarea.value = currentSession["textarea"] ? currentSession["textarea"] : textarea.value;
range.value = currentSession["range"] ? currentSession["range"] : range.value;
checkbox.checked = currentSession["checkbox"] ? currentSession["checkbox"] : checkbox.checked;
theme.value = currentSession["theme"] ? currentSession["theme"] : theme.value;
write();
}
function write() {
textarea.innerHTML = textarea.value;
p.textContent = textarea.value;
output.textContent = range.value;
span.textContent = checkbox.checked;
document.body.className = theme.value;
createSessionStorage();
}
textarea.addEventListener("input", write);
range.addEventListener("input", write);
checkbox.addEventListener("change", write);
theme.addEventListener("change", write);
applySessionStorage();
</script>
</body>
</html>
<!-- end snippet -->
答案2
得分: 0
以下是翻译好的部分:
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
var textarea = document.querySelector('textarea');
var p = document.querySelector('p');
var range = document.querySelector('input[type="range"]');
var output = document.querySelector('output');
var checkbox = document.querySelector('input[type="checkbox"]');
var span = document.querySelector('span');
document.querySelector('textarea').value = 'Hello, world!';
document.querySelector('input[type="range"]').value = 50;
document.querySelector('input[type="checkbox"]').checked = false;
function write() {
p.textContent = textarea.value;
output.textContent = range.value;
span.textContent = checkbox.checked;
}
textarea.addEventListener('input', write);
range.addEventListener('input', write);
checkbox.addEventListener('change', write);
write();
<!-- language: lang-css -->
body {
display: grid;
grid-template-columns: repeat(2, max-content);
gap: 1em 0.5em;
}
<!-- language: lang-html -->
<textarea>Hello, world!</textarea>
<p></p>
<input type="range">
<output></output>
<input type="checkbox">
<span></span>
<!-- end snippet -->
希望这个翻译对您有所帮助。
英文:
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
var textarea = document.querySelector('textarea');
var p = document.querySelector('p');
var range = document.querySelector('input[type="range"]');
var output = document.querySelector('output');
var checkbox = document.querySelector('input[type="checkbox"]');
var span = document.querySelector('span');
document.querySelector('textarea').value = 'Hello, world!';
document.querySelector('input[type="range"]').value = 50;
document.querySelector('input[type="checkbox"]').checked = false;
function write() {
p.textContent = textarea.value;
output.textContent = range.value;
span.textContent = checkbox.checked;
}
textarea.addEventListener('input', write);
range.addEventListener('input', write);
checkbox.addEventListener('change', write);
write();
<!-- language: lang-css -->
body {
display: grid;
grid-template-columns: repeat(2, max-content);
gap: 1em 0.5em;
}
<!-- language: lang-html -->
<textarea>Hello, world!</textarea>
<p></p>
<input type="range">
<output></output>
<input type="checkbox">
<span></span>
<!-- end snippet -->
答案3
得分: 0
以下是您要翻译的内容:
当浏览器复制标签页时,它不会触发textarea/range/checkbox的更改事件。之后,在复制标签页后,当DOM加载完成时,它将设置其值。因此,您需要为您的write
函数提供一些延迟,以便浏览器完成设置元素的内容,然后我们的写函数将获得元素的正确值。
更新后的代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Duplicate browser tab ignores current values</title>
<style>
body {
display: grid;
grid-template-columns: repeat(2, max-content);
gap: 1em 0.5em;
}
</style>
</head>
<body>
<textarea>Hello, world!</textarea>
<p></p>
<input type="range">
<output></output>
<input type="checkbox">
<span></span>
<script>
var textarea = document.querySelector('textarea');
var p = document.querySelector('p');
var range = document.querySelector('input[type="range"]');
var output = document.querySelector('output');
var checkbox = document.querySelector('input[type="checkbox"]');
var span = document.querySelector('span');
function write() {
p.textContent = textarea.value;
output.textContent = range.value;
span.textContent = checkbox.checked;
}
textarea.addEventListener('input', write);
range.addEventListener('input', write);
checkbox.addEventListener('change', write);
setTimeout(function() {
write();
}, 10);
</script>
</body>
</html>
但是Chrome在复制的标签页中不会复制复选框的状态。
要检测Chrome/Firefox/IE(Chromium)上的标签页是否被复制或未被复制,可以使用以下JavaScript:
// 类型值 => 详细信息
// 0 => 新标签页
// 1 => 重新加载标签页
// 2 => 复制标签页
window.performance.navigation.type == 2
英文:
When the browser duplicates tabs, it will not fire the change event of textarea/range/checkbox. After, duplicating the tab, when the dom is loaded it will set its value. So, you need to provide some delay for your write
function so the browser finishes setting the content of an element and then our write function will get the proper value of an element.
Updated code as following:
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Duplicate browser tab ignores current values</title>
<style>
body {
display: grid;
grid-template-columns: repeat(2, max-content);
gap: 1em 0.5em;
}
</style>
</head>
<body>
<textarea>Hello, world!</textarea>
<p></p>
<input type="range">
<output></output>
<input type="checkbox">
<span></span>
<script>
var textarea = document.querySelector('textarea');
var p = document.querySelector('p');
var range = document.querySelector('input[type="range"]');
var output = document.querySelector('output');
var checkbox = document.querySelector('input[type="checkbox"]');
var span = document.querySelector('span');
function write() {
p.textContent = textarea.value;
output.textContent = range.value;
span.textContent = checkbox.checked;
}
textarea.addEventListener('input', write);
range.addEventListener('input', write);
checkbox.addEventListener('change', write);
setTimeout(function() {
write();
}, 10);
</script>
</body>
</html>
But Chrome is not copying state of checkbox in duplicated tab.
To detect if tab is duplicated or not on Chrome/Firefox/IE(Chromium) you can use following JS:
// type value => details
// 0 => new tab
// 1 => reload tab
// 2 => duplicate tab
window.performance.navigation.type == 2
答案4
得分: 0
我观察到,如果覆盖复选框/单选按钮的value
,则复制复选框和单选按钮的操作是可靠的。
在以下示例中,复选框和文本字段都正确复制,包括它们的文本表示。主题也被复制。
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
function update() {
for (var i of document.querySelectorAll("input")) {
if (i.type === "checkbox" || i.type === "radio") {
var value = i.value;
i.value = "";
i.value = value;
}
i.nextElementSibling.textContent = i.value;
}
document.body.className = document.querySelector("select").value;
}
<!-- language: lang-css -->
body.Dark {
color: white;
background: black;
}
<!-- language: lang-html -->
<body onload="update()">
<input type="checkbox" onchange="update()" /><span></span>
<input type="radio" name="radio" value="A" onchange="update()" /><span></span>
<input type="radio" name="radio" value="B" onchange="update()" /><span></span>
<input type="text" onchange="update()" /><span></span>
<select onchange="update()">
<option>Light</option>
<option>Dark</option>
</select>
</body>
<!-- end snippet -->
请注意,我只提供了代码的翻译部分,没有包含其他内容。
英文:
I observed that the duplication of checkboxes and radiobuttons works reliably if the value
of the checkbox/radiobutton is overwritten.
Both checkbox and text field are duplicated correctly, along with their textual representations, in the following example. The theme is also duplicated.
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
function update() {
for (var i of document.querySelectorAll("input")) {
if (i.type === "checkbox" || i.type === "radio") {
var value = i.value;
i.value = "";
i.value = value;
}
i.nextElementSibling.textContent = i.value;
}
document.body.className = document.querySelector("select").value;
}
<!-- language: lang-css -->
body.Dark {
color: white;
background: black;
}
<!-- language: lang-html -->
<body onload="update()">
<input type="checkbox" onchange="update()" /><span></span>
<input type="radio" name="radio" value="A" onchange="update()" /><span></span>
<input type="radio" name="radio" value="B" onchange="update()" /><span></span>
<input type="text" onchange="update()" /><span></span>
<select onchange="update()">
<option>Light</option>
<option>Dark</option>
</select>
</body>
<!-- end snippet -->
答案5
得分: 0
我在Firefox和Brave(基于Chromium)上测试了您提供的步骤,它们在标签复制时表现不同。
- Firefox会保留"form"值并调用事件侦听器以反映更改(仅当您监听
<select>
上的'input'
而不是'change'
时起作用。原因是'change'
仅在用户主动选择值时触发,而这在复制时不会发生。) - Brave会保留"form"值,但不会调用事件侦听器,因此您的输出元素不会反映初始值(主题也不会更改)。
这种行为差异清楚地显示了"标签复制"并非标准化,而是每个浏览器供应商根据其最佳判断实现的功能。每个浏览器都可以决定复制原始标签状态的程度,这可能会影响用户体验、性能甚至安全性,并且正如我的测试所显示的那样,复制的程度在各个浏览器之间可能会有很大差异。
因此,正如您所看到的,不能依赖完美复制或在不同浏览器之间保持一致性。这意味着您需要一些持久性来确保表单中的值在复制的标签中得到保存并重复使用。
正如其他人已经提到的,您可以使用sessionStorage或localStorage进行测试或将值保存在某种数据库中作为更强大的解决方案。
英文:
I tested the steps you provided on Firefox and Brave (based on Chromium) and they both behave differently on tab duplication.
- Firefox persists both "form" values and calls event listeners to reflect the change (works only when you instead of
'change'
listen to'input'
on your<select>
. The reason is that'change'
only fires when user actively selects a value, which doesn't happen during duplication.) - Brave keeps the "form" values but doesn't call the event listeners, so your output elements don't reflect the initial values (and the theme doesn't get changed).
This difference in behaviour shows well that the "tab duplication" isn't standardized, but rather a feature that each browser vendor implements the way it sees best. Each browser gets to decide how much of the original tab's state gets duplicated, which can have UX, performance and perhaps even security implications, and as my testing has shown the degree of duplication can differ substantially across browsers.
So as you can see, one can't rely on a perfect duplication nor consistency across different browsers. This means that you need some kind of persistency to make sure the values in the form get saved and reused in duplicated tab.
As others have already mentioned, you can use sessionStorage or localStorage for testing or save the values in some kind of a database as a more robust solution.
答案6
得分: -1
当复制标签时,输入字段的值会被保存,但 :checked
属性除外。作为选择,你可以将 :checked
属性保存在 sessionStorage
中。
为了测试,我创建了一个小的示例。将脚本分为三部分:
- 保存和恢复
:checked
- 初始显示输入值
- 输入更改后显示值
我在这里添加了所有的代码:
更新:添加更改主题。
<!DOCTYPE html>
<html lang="en">
<head>
<title>Duplicate browser tab ignores current values</title>
<style>
body.dark {
color: white;
background-color: black;
}
</style>
</head>
<body>
<ul>
<li>
<textarea>Hello, world!</textarea>
<span></span>
</li>
<li>
<input type="range">
<span></span>
</li>
<li>
<input type="checkbox">
<span></span>
</li>
<li>
<input type="radio" name="radio" value="1" checked>
<input type="radio" name="radio" value="2">
<span></span>
</li>
<li>
<select>
<option>light</option>
<option>dark</option>
</select>
<span></span>
</li>
</ul>
<script>
/* 保存和恢复 :checked */
const checkedElements = document.querySelectorAll('[type="checkbox"], [type="radio"]');
window.addEventListener('load', restoreCheckedState)
function restoreCheckedState() {
if (sessionStorage.getItem('checkedState')) {
const storage = JSON.parse(sessionStorage.getItem('checkedState'));
checkedElements.forEach( (el, index) => el.checked = storage[index] );
// console.log('restore', sessionStorage.getItem('checkedState'));
}
}
checkedElements.forEach( el => el.addEventListener('change', saveCheckedState) );
function saveCheckedState() {
const checkeds = [];
checkedElements.forEach( el => checkeds.push(el.checked) );
sessionStorage.setItem( 'checkedState', JSON.stringify(checkeds) );
// console.log('saved', sessionStorage.getItem('checkedState'));
}
/* 初始显示值 */
window.addEventListener('load', () => {
inputs.forEach( el => showInputValue(el) );
changeTheme( document.querySelector('select').value );
})
/* 输入更改后显示值 */
const inputs = document.querySelectorAll('input, textarea, select');
inputs.forEach( el => el.addEventListener( 'input', () => showInputValue(el) ) );
function showInputValue(input) {
const span = input.closest('li').querySelector('span');
if ( input.type === 'checkbox' ) {
span.textContent = input.getAttribute('value') ? input.value : input.checked;
} else if ( input.type === 'radio' ) {
if ( input.name ){
span.textContent = document.querySelector(`[name="${input.name}"]:checked`).value
} else {
span.textContent = input.checked;
}
} else {
span.textContent = input.value;
}
}
/* 更改主题 */
document.querySelector('select').addEventListener('change', function() {
changeTheme(this.value);
})
function changeTheme(theme = 'light') {
document.body.classList.remove('light', 'dark');
document.body.classList.add(theme);
}
</script>
</body>
</html>
英文:
When duplicating a tab, input fields values are saved, with exception :checked
props.
As an option, you can save :checked
props in sessionStorage
.
For testing, I created a small example.
I divided the scripts into three parts:
- save and restore
:checked
- initial show inputs values
- show value after input change
I am adding all the code here:
Update: added change theme.
<!-- begin snippet: js hide: true console: false babel: false -->
<!-- language: lang-html -->
<!DOCTYPE html>
<html lang="en">
<head>
<title>Duplicate browser tab ignores current values</title>
<style>
body.dark {
color: white;
background-color: black;
}
</style>
</head>
<body>
<ul>
<li>
<textarea>Hello, world!</textarea>
<span></span>
</li>
<li>
<input type="range">
<span></span>
</li>
<li>
<input type="checkbox">
<span></span>
</li>
<li>
<input type="radio" name="radio" value="1" checked>
<input type="radio" name="radio" value="2">
<span></span>
</li>
<li>
<select>
<option>light</option>
<option>dark</option>
</select>
<span></span>
</li>
</ul>
<script>
/* save and restore :checked */
const checkedElements = document.querySelectorAll('[type="checkbox"], [type="radio"]');
window.addEventListener('load', restoreCheckedState)
function restoreCheckedState() {
if (sessionStorage.getItem('checkedState')) {
const storage = JSON.parse(sessionStorage.getItem('checkedState'));
checkedElements.forEach( (el, index) => el.checked = storage[index] );
// console.log('restore', sessionStorage.getItem('checkedState'));
}
}
checkedElements.forEach( el => el.addEventListener('change', saveCheckedState) );
function saveCheckedState() {
const checkeds = [];
checkedElements.forEach( el => checkeds.push(el.checked) );
sessionStorage.setItem( 'checkedState', JSON.stringify(checkeds) );
// console.log('saved', sessionStorage.getItem('checkedState'));
}
/* initial show values */
window.addEventListener('load', () => {
inputs.forEach( el => showInputValue(el) );
changeTheme( document.querySelector('select').value );
})
/* show value after input change */
const inputs = document.querySelectorAll('input, textarea, select');
inputs.forEach( el => el.addEventListener( 'input', () => showInputValue(el) ) );
function showInputValue(input) {
const span = input.closest('li').querySelector('span');
if ( input.type === 'checkbox' ) {
span.textContent = input.getAttribute('value') ? input.value : input.checked;
} else if ( input.type === 'radio' ) {
if ( input.name ){
span.textContent = document.querySelector(`[name="${input.name}"]:checked`).value
} else {
span.textContent = input.checked;
}
} else {
span.textContent = input.value;
}
}
/* theme change */
document.querySelector('select').addEventListener('change', function() {
changeTheme(this.value);
})
function changeTheme(theme = 'light') {
document.body.classList.remove('light', 'dark');
document.body.classList.add(theme);
}
</script>
</body>
</html>
<!-- end snippet -->
答案7
得分: -1
write()
必须在初始加载时调用。
document.addEventListener("DOMContentLoaded", () => {
write();
})
这应该可以工作。
英文:
write()
must get called on initial site load.
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
document.addEventListener("DOMContentLoaded",()=>{
write();
})
<!-- end snippet -->
This should work.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论