javascript 重复的浏览器选项卡忽略表单元素的当前值

qlvxas9a  于 2023-04-28  发布在  Java
关注(0)|答案(7)|浏览(107)

问题

在浏览器中复制选项卡时,form元素的当前值将被忽略。在Windows 11计算机上的最新版本的Chrome、Firefox和Edge中进行了测试。

示例代码及demo

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();
body {
  display: grid;
  grid-template-columns: repeat(2, max-content);
  gap: 1em 0.5em;
}

body.Dark {
  color: white;
  background: black;
}
<textarea>Hello, world!</textarea>
<p></p>
<input type="range">
<output></output>
<input type="checkbox">
<span></span>
<select>
  <option>Light</option>
  <option>Dark</option>
</select>

DEMO

问题重现步骤

1.打开demo page或创建自己的。
1.更改textareainput s和select默认值。
1.复制选项卡。

  1. poutputspan元素没有显示预期的文本内容,主题仍然很轻。

提问

1.为什么会这样呢?
1.解决办法是什么?

cld4siwp

cld4siwp1#

1.为什么会这样呢?
因为浏览器选项卡中的表单不存储当前值。
1.解决办法是什么?
两个解决方案解决这个问题:

1.查询参数的使用

Query params将当前值存储为URL Search Params,因此当通过new URL(document.location).searchParams复制浏览器选项卡时,可以访问它以重新填充数据。
产品编号:https://playcode.io/queryparams
演示:https://queryparams.playcode.io

<!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>

2.使用会话存储

会话存储器将当前值存储为session data,以便在通过.getItem方法复制浏览器选项卡时可以访问它以重新填充数据。
产品编号:https://playcode.io/sessionstorage
演示:https://sessionstorage.playcode.io

<!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>
aemubtdh

aemubtdh2#

我观察到,如果复选框/单选按钮的value被覆盖,复选框和单选按钮的复制工作可靠。
在下面的示例中,复选框和文本字段沿着它们的文本表示都正确复制。主题也是重复的。

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;
}
body.Dark {
  color: white;
  background: black;
}
<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>
8hhllhi2

8hhllhi23#

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();
body {
      display: grid;
      grid-template-columns: repeat(2, max-content);
      gap: 1em 0.5em;
    }
<textarea>Hello, world!</textarea>
    <p></p>
    <input type="range">
    <output></output>
    <input type="checkbox">
    <span></span>
tpgth1q7

tpgth1q74#

当浏览器复制标签时,它不会触发textarea/range/checkbox的change事件。之后,复制选项卡,当dom被加载时,它将设置它的值。因此,您需要为write函数提供一些延迟,以便浏览器完成设置元素的内容,然后我们的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)上重复或不重复,您可以使用以下JS:

// type value => details
// 0 => new tab
// 1 => reload tab
// 2 => duplicate tab
window.performance.navigation.type == 2
s4chpxco

s4chpxco5#

我在Firefox和Brave(基于Chromium)上测试了您提供的步骤,它们在标签复制上的行为都不同。

  • Firefox持久化“form”值并调用事件侦听器来反映更改 (仅当您而不是'change'<select>上侦听'input'时才有效。原因是'change'仅在用户主动选择值时才会触发,而在复制过程中不会发生这种情况。)
  • Brave保留“form”值,但不调用事件侦听器,因此输出元素不会反映初始值(主题也不会更改)。

这种行为上的差异很好地表明了“标签复制”不是标准化的,而是每个浏览器供应商以其最佳方式实现的功能。每个浏览器都可以决定原始选项卡的状态有多少被复制,这可能会对用户体验,性能甚至安全性产生影响,正如我的测试所示,不同浏览器之间的复制程度可能会有很大差异。
因此,正如你所看到的,一个不能依赖于完美的复制或一致性在不同的浏览器之间。这意味着您需要某种持久性,以确保表单中的值在重复的选项卡中得到保存和重用。
正如其他人已经提到的,您可以使用sessionStoragelocalStorage进行测试,或者将值保存在某种数据库中,作为更健壮的解决方案。

wz3gfoph

wz3gfoph6#

复制选项卡时,输入字段的值会被保存,但:checked属性除外。
作为一个选项,您可以将:checked prop 保存在sessionStorage中。
为了测试,我创建了一个小的example
我把剧本分成三部分:
1.保存和恢复:checked
1.初始显示输入值
1.输入更改后显示值
我在这里添加所有代码:

  • 更新:添加了更改主题。*
<!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>
ct3nt3jp

ct3nt3jp7#

write()必须在初始站点加载时被调用。

document.addEventListener("DOMContentLoaded",()=>{
    write();
})

这个应该能用

相关问题