Дублирующая вкладка браузера игнорирует текущие значения элементов формы

Проблема

Когда вы дублируете вкладку в браузере, текущие значения элементов form игнорируются. Протестировано в последних версиях Chrome, Firefox и Edge на компьютере с Windows 11.

Пример кода и демонстрация

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>

ДЕМО

Действия по воспроизведению проблемы

  1. Откройте демо-страницу или создайте свою.
  2. Измените значения по умолчанию textarea, inputs и select.
  3. Дублируйте вкладку.

  1. Элементы p, output и span не отображают ожидаемое текстовое содержимое, а тема по-прежнему светлая.

Вопрос

  1. Почему это происходит?
  2. Какое решение?

Отвечает ли это на ваш вопрос? Продублировать вкладку в Chrome без перезагрузки страницы?

pilchard 17.04.2023 13:10

@pilchard: Спасибо, но это не то же самое, и это не дает рабочего решения.

Mori 17.04.2023 13:18

«Изменить текстовое поле и ввести значения по умолчанию». - ввод данных в эти поля не изменяет их значения по умолчанию, а только их текущее значение.

CBroe 19.04.2023 13:45

То, что вы хотите здесь, просто не является поведением по умолчанию. Если вам это нужно, вам придется облегчить это самостоятельно — сохранять текущие значения полей в localStorage всякий раз, когда они изменяются, а затем, когда второй экземпляр вашей страницы загружается в новую вкладку — считывать значения обратно из localStorage и устанавливать их для поля ввода в этом документе.

CBroe 19.04.2023 13:46

Использование localStorage для сохранения значения и восстановления его обратно на дублирующей вкладке будет работать, но если вы сделаете несколько дублирующих вкладок, будет сложно найти, какое значение вкладки нужно восстановить.

Dhaval Gajjar 19.04.2023 14:55

@CBroe: Как насчет sessionStorage?

Mori 20.04.2023 08:47

@ Мори, как насчет этого? developer.mozilla.org/en-US/docs/Web/API/Window/sessionStora‌​ge: «Всякий раз, когда документ загружается на определенной вкладке в браузере, создается уникальный сеанс страницы, который назначается этому конкретному вкладка. Этот сеанс страницы действителен только для этой конкретной вкладки». - так явно не подходит для того, что вы хотите здесь.

CBroe 20.04.2023 08:51

@CBroe: из того же источника: «Дублирование вкладки копирует sessionStorage вкладки в новую вкладку».

Mori 20.04.2023 09:12

Хорошо, если это единственный вариант использования, для которого вам это нужно (а не, скажем, если пользователь вручную открывает тот же URL-адрес на другой вкладке), это также подойдет.

CBroe 20.04.2023 09:15
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
4
9
471
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

    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>

Когда браузер дублирует вкладки, он не запускает событие изменения 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), вы можете использовать следующий JS:

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

«Но Chrome не копирует состояние флажка на дублированной вкладке». Он ведет себя непоследовательно: иногда работает, а иногда нет.

Mori 19.04.2023 19:58

Хорошо. Мы не можем использовать localStorage. Как если бы мы продублировали вкладку один раз, мы получили бы данные из localStorage и установили. После дублирования мы меняем значение в дублированной вкладке, и теперь мы дублируем вкладку из первой, тогда localStorage будет иметь значение из последнего изменения, поэтому в форме будут отображаться неправильные данные.

Dhaval Gajjar 20.04.2023 05:54

У нас такая же проблема с sessionStorage?

Mori 20.04.2023 10:46

Вы можете использовать sessionStorage. Но если вы дублируете вкладку и вносите изменения, а затем снова дублируете исходную вкладку, вы получаете данные из дублированной вкладки. localStorage будет использоваться всеми вкладками одного домена.

Dhaval Gajjar 20.04.2023 11:05

Более уместно вызывать функцию write() для события window.onload. Но это все равно не установит свойство :checked, а только обновит textContent в элементах.

imhvost 20.04.2023 23:48

Интересно, почему вы добавили текстовое содержимое по умолчанию в элементы p, output и span? Разве это не лишнее?

Mori 22.04.2023 05:25

@Mori Он уже добавлен в образец. Но его можно удалить, так как метод записи всегда будет вызываться при загрузке страницы. Таким образом, он обновит p, output и span. Обновленный ответ.

Dhaval Gajjar 22.04.2023 07:52

@imhvost Не работает с загрузкой. Поскольку браузер устанавливает значение ввода/диапазона после загрузки. Поэтому я использовал тайм-аут.

Dhaval Gajjar 22.04.2023 07:54

Я не уверен, но можно ли добиться этой задержки с помощью функции async вместо setTimeout?

Mori 22.04.2023 08:22

Нет, насколько я знаю. Если мы добавим async для записи функции без setTimeout, она будет вести себя так же, как и у вас. Если вы добавите ожидание при вызове функции записи, она перестанет работать.

Dhaval Gajjar 22.04.2023 08:33

Задержка 10 мс — это просто произвольное число, и фактическое время отличается от браузера к браузеру. Может ли рассчитано время, необходимое браузеру для загрузки текущего значения в дублирующую вкладку?

Mori 22.04.2023 08:58

Это также зависит от того, насколько велик ваш сайт. В настоящее время с вашим образцом работает 10 мс. Но нужно проверить реальный сайт и другой браузер и найти оптимальное значение.

Dhaval Gajjar 22.04.2023 09:03

Проверил - нормально работает на window.onload. Так что setTimeout не нужен. Но это все равно не установит :checked, только обновит textContent в элементах.

imhvost 22.04.2023 10:32

При дублировании вкладки сохраняются значения полей ввода, за исключением :checked props.
Как вариант, вы можете сохранить :checked реквизит в sessionStorage.

Для тестирования я создал небольшой пример.
Я разделил скрипты на три части:

  1. сохранить и восстановить :checked
  2. начальное отображение входных значений
  3. показать значение после изменения ввода

Я добавляю весь код здесь:

Обновление: добавлена ​​тема смены.

<!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.info('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.info('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>

Я заметил, что дублирование флажков и радиокнопок работает надежно, если 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>

А как же <input type = "radio">?

imhvost 22.04.2023 10:24

Кажется, это работает. Но следует учитывать, что в реальном случае <input type = "radio"> будет иметь значение, отличное от "on".

imhvost 22.04.2023 11:48

Конечно. Я снова отредактировал свой ответ.

Heiko Theißen 22.04.2023 13:37

Я проверил шаги, которые вы предоставили, в Firefox и Brave (на основе Chromium), и они оба ведут себя по-разному при дублировании вкладок.

  • Firefox сохраняет как значения «формы», так и вызывает прослушиватели событий, чтобы отразить изменение (работает только тогда, когда вы вместо 'change' слушаете 'input' на своем <select>. Причина в том, что 'change' срабатывает только тогда, когда пользователь активно выбирает значение, чего не происходит во время дублирование.)
  • Brave сохраняет значения «формы», но не вызывает прослушиватели событий, поэтому ваши выходные элементы не отражают начальные значения (и тема не изменяется).

Эта разница в поведении хорошо показывает, что «дублирование вкладок» не стандартизировано, а представляет собой функцию, которую каждый поставщик браузера реализует так, как он видит лучше всего. Каждый браузер решает, какая часть исходного состояния вкладки будет дублироваться, что может иметь последствия для UX, производительности и, возможно, даже безопасности, и, как показало мое тестирование, степень дублирования может существенно различаться в разных браузерах.

Итак, как видите, нельзя полагаться на идеальное дублирование или согласованность в разных браузерах. Это означает, что вам нужно какое-то постоянство, чтобы убедиться, что значения в форме сохраняются и повторно используются на дублированной вкладке.

Как уже упоминалось, вы можете использовать sessionStorage или localStorage для тестирования или сохранить значения в какой-либо базе данных в качестве более надежного решения.

write() должен вызываться при начальной загрузке сайта.

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

Это должно работать.

Ответ принят как подходящий
  1. Почему это происходит?

Потому что форма на вкладке браузера не сохраняет текущее значение.


  1. Какое решение?

Два решения для решения этой проблемы:

1. Использование параметров запроса

Параметры запроса будут хранить текущее значение как Параметры поиска URL, чтобы к нему можно было получить доступ для пополнения данных при дублировании вкладки браузера с помощью 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. Использование хранилища сеансов

Хранилище сеанса будет хранить текущее значение как данные сеанса, чтобы к нему можно было получить доступ для пополнения данных при дублировании вкладки браузера методом .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>

Другие вопросы по теме