16 липня 2023 р.

Спливаючі вікна та методи window

Спливаюче вікно є одним із найстаріших методів показу додаткового документа користувачеві.

В основному, ви просто запускаєте:

window.open('https://javascript.info/')

…І відкриється нове вікно з заданою URL-адресою. Більшість сучасних браузерів налаштовано на відкриття URL-адрес у нових вкладках замість окремих вікон.

Спливаючі вікна існують з давніх часів. Початкова ідея полягала в тому, щоб показати інший вміст, не закриваючи основне вікно. На даний момент є інші способи зробити це: ми можемо динамічно завантажувати вміст за допомогою fetch і показувати його у динамічно згенерованому <div>. Отже, спливаючі вікна – це не те, що ми використовуємо щодня.

Крім того, спливаючі вікна не дуже зручні для мобільних пристроїв, які не показують кілька вікон одночасно.

Проте є завдання, де все ще використовуються спливаючі вікна, напр. для авторизації OAuth (вхід через Google/Facebook/…), тому що:

  1. Спливаюче вікно – це окреме вікно, яке має власне незалежне середовище JavaScript. Тому відкриття спливаючого вікна із стороннього ненадійного сайту безпечно.
  2. Відкрити спливаюче вікно дуже легко.
  3. Спливаюче вікно може здійснювати навігацію (змінювати URL-адресу) та надсилати повідомлення в основне вікно.

Блокування спливаючих вікон

У минулому погані сайти часто зловживали спливаючими вікнами. Погана сторінка може відкрити безліч спливаючих вікон з рекламою. Тому зараз більшість браузерів намагаються блокувати спливаючі вікна та захистити користувача.

Більшість браузерів блокують спливаючі вікна, якщо вони викликані за межами обробників подій, ініційованих користувачем, як-от onclick.

Наприклад:

// спливаюче вікно заблоковано
window.open('https://javascript.info');

// спливаюче вікно дозволено
button.onclick = () => {
  window.open('https://javascript.info');
};

Таким чином користувачі дещо захищені від небажаних спливаючих вікон, але функціональність не відключається повністю.

window.open

Синтаксис відкриття спливаючого вікна: window.open(url, name, params):

url
URL-адреса для завантаження в нове вікно.
name
Ім’я нового вікна. Кожне вікно має window.name, і тут ми можемо вказати, яке вікно використовувати для спливаючого вікна. Якщо вікно з такою назвою вже є – в ньому відкривається дана URL-адреса, інакше відкривається нове вікно.
params
Рядок конфігурації для нового вікна. Він містить налаштування, розділені комою. У параметрах не повинно бути пробілів, наприклад: width=200,height=100.

Налаштування для params:

  • Позиція:
    • left/top (числові) – координати верхнього лівого кута вікна на екрані. Є обмеження: нове вікно не можна розташувати за екраном.
    • width/height (числові) – ширина та висота нового вікна. Існує обмеження на мінімальну ширину/висоту, тому створити невидиме вікно неможливо.
  • Характеристики вікна:
    • menubar (так/ні) – показує або приховує меню браузера в новому вікні.
    • toolbar (так/ні) – показує або приховує панель навігації браузера (назад, вперед, перезавантаження тощо) у новому вікні.
    • location (так/ні) – показує або приховує поле URL у новому вікні. FF та IE не дозволяють приховати його за замовчуванням.
    • status (так/ні) – показує або приховує рядок стану. Знову ж таки, більшість браузерів змушують його показувати.
    • resizable (так/ні) – дозволяє вимкнути зміну розміру для нового вікна. Не рекомендовано.
    • scrollbars (так/ні) – дозволяє вимкнути смуги прокрутки для нового вікна. Не рекомендовано.

Існує також ряд менш підтримуваних функцій браузера, які зазвичай не використовуються. Перегляньте приклади window.open у MDN.

Приклад: мінімалістичне вікно

Давайте відкриємо вікно з мінімальним набором функцій, щоб побачити, який із них браузер дозволяє відключити:

let params = `scrollbars=no,resizable=no,status=no,location=no,toolbar=no,menubar=no,
width=0,height=0,left=-1000,top=-1000`;

open('/', 'test', params);

Тут більшість “функцій вікна” вимкнено, а вікно розміщено за екраном. Запустіть і подивіться, що насправді станеться. Більшість браузерів “виправляють” дивні речі, як-от нульове значення width/height та від’ємні left/top. Наприклад, Chrome відкриває таке вікно з повною шириною/висотою, щоб воно займало весь екран.

Давайте додамо звичайні параметри позиціонування та прийнятні координати width, height, left, top:

let params = `scrollbars=no,resizable=no,status=no,location=no,toolbar=no,menubar=no,
width=600,height=300,left=100,top=100`;

open('/', 'test', params);

Більшість браузерів показують наведений вище приклад як потрібно.

Правила для пропущених налаштувань:

  • Якщо у виклику open немає третього аргументу або він порожній, то використовуються параметри вікна за замовчуванням.
  • Якщо є рядок параметрів, але деякі yes/no параметри пропущені, тоді вважається, що вони мають значення no. Тому якщо ви вказуєте параметри, переконайтеся, що ви явно встановили для всіх необхідних значення так.
  • Якщо в параметрах немає left/top, тоді браузер намагається відкрити нове вікно біля останнього відкритого вікна.
  • Якщо немає width/height, тоді нове вікно матиме такий самий розмір, як і останнє відкрите.

Доступ до спливаючого вікна з window

Виклик open повертає посилання на нове вікно. Його можна використовувати для маніпулювання його властивостями, зміни розташування тощо.

У цьому прикладі ми генеруємо спливаюче вікно з JavaScript:

let newWin = window.open("about:blank", "hello", "width=200,height=200");

newWin.document.write("Привіт, світ!");

А тут ми змінюємо вміст після завантаження:

let newWindow = open('/', 'example', 'width=300,height=300')
newWindow.focus();

alert(newWindow.location.href); // (*) about:blank, завантаження ще не почалось

newWindow.onload = function() {
  let html = `<div style="font-size:30px">Ласкаво просимо!</div>`;
  newWindow.document.body.insertAdjacentHTML('afterbegin', html);
};

Зверніть увагу: одразу після window.open нове вікно ще не завантажено. Це демонструє alert в рядку “(*)”. Тому ми чекаємо onload, щоб змінити його. Ми також можемо використовувати обробник DOMContentLoaded для newWin.document.

Політика одного джерела

Вікна можуть вільно отримувати доступ до вмісту один одного, лише якщо вони мають одне походження (однаковий protocol://domain:port).

Інакше, напр. якщо основне вікно з сайту site.com, а спливаюче з gmail.com, це неможливо з міркувань безпеки користувача. Додаткову інформацію див. у розділі Міжвіконна комунікація.

Доступ до window зі спливаючого вікна

Спливаюче вікно також може отримати доступ до основного вікна за допомогою посилання window.opener. Воно має значення null для всіх вікон, крім спливаючих.

Якщо ви запустите наведений нижче код, він замінить вміст основного вікна (поточного) на “Тест”:

let newWin = window.open("about:blank", "hello", "width=200,height=200");

newWin.document.write(
  "<script>window.opener.document.body.innerHTML = 'Тест'<\/script>"
);

Таким чином, зв’язок між вікнами є двонаправленим: основне вікно та спливаюче вікно мають посилання один на одного.

Закриття спливаючого вікна

Щоб закрити вікно: win.close().

Щоб перевірити, чи закрите вікно: win.closed.

Технічно метод close() доступний для будь-якого window, але window.close() ігнорується більшістю браузерів, якщо window не створено за допомогою window.open(). Тож він працюватиме лише у спливаючому вікні.

Властивість closed має значення true, якщо вікно закрите. Це корисно, щоб перевірити, чи відкрите спливаюче вікно (або основне вікно) чи ні. Користувач може закрити його в будь-який час, і наш код повинен враховувати цю можливість.

Цей код завантажується, а потім закриває вікно:

let newWindow = open('/', 'example', 'width=300,height=300');

newWindow.onload = function() {
  newWindow.close();
  alert(newWindow.closed); // true
};

Переміщення та зміна розміру

Існують методи переміщення/зміни розміру вікна:

win.moveBy(x,y)
Перемістити вікно відносно поточної позиції x пікселів праворуч і y пікселів вниз. Допускаються від’ємні значення (переміщення вліво/вгору).
win.moveTo(x,y)
Перемістити вікно до координат (x,y) на екрані.
win.resizeBy(width,height)
Змінити розмір вікна на задану width/height відносно поточного розміру. Допускаються від’ємні значення.
win.resizeTo(width,height)
Змінити розмір вікна на заданий.

Існує також подія window.onresize.

Тільки спливаючі вікна

Щоб запобігти зловживанням, браузер зазвичай блокує ці методи. Вони надійно працюють лише на відкриті нами спливаючі вікна, які не мають додаткових вкладок.

Не можна згорнути/розгорнути

JavaScript не має можливості згортати або розгортати вікно. Ці функції рівня ОС приховані від Frontend-розробників.

Методи переміщення/змінення розміру не працюють для розгорнутих/згорнутих вікон.

Прокручування вікна

Ми вже говорили про прокручування вікна в розділі Розміри вікна і прокрутка.

win.scrollBy(x,y)
Прокрутить вікно x пікселів праворуч і y вниз відносно поточного прокручування. Допускаються від’ємні значення.
win.scrollTo(x,y)
Прокрутить вікно до заданих координат (x,y).
elem.scrollIntoView(top = true)
Прокрутить вікно, щоб elem відображався вгорі (за замовчуванням) або внизу для elem.scrollIntoView(false).

Існує також подія window.onscroll.

Фокусування/розфокусування на вікні

Теоретично існують методи window.focus() і window.blur() для фокусування/розфокусування на вікні. Є також події focus/blur, які дозволяють вловити момент, коли відвідувач фокусується на вікні та перемикається в інше місце.

Хоча на практиці вони сильно обмежені, бо в минулому “погані” сторінки ними зловживали.

Наприклад, подивіться на цей код:

window.onblur = () => window.focus();

Коли користувач намагається перемикнути фокус із поточного вікна (window.onblur), фокус повертається назад. Мета полягає в тому, щоб “заблокувати” користувача у window.

Тож браузерам довелося ввести багато обмежень, щоб заборонити такий код і захистити користувача від реклами та шкідливих сторінок. Ці обмеження залежать від браузера.

Наприклад, мобільний браузер зазвичай повністю ігнорує window.focus(). Також фокусування не працює, коли спливаюче вікно відкривається в окремій вкладці, а не в новому вікні.

Проте є деякі випадки використання, коли такі виклики працюють і можуть бути корисними.

Наприклад:

  • Коли ми відкриваємо спливаюче вікно, може бути гарною ідеєю запустити в ньому newWindow.focus(). Про всяк випадок, для деяких комбінацій ОС/браузер це гарантує, що користувач зараз у новому вікні.
  • Якщо ми хочемо відстежувати, коли відвідувач дійсно використовує нашу веб-програму, ми можемо відстежувати window.onfocus/onblur. Це дозволяє нам призупинити/відновити дії на сторінці, анімацію тощо. Але, будь ласка, зверніть увагу, що подія blur означає, що відвідувач вийшов із вікна, але він все одно може спостерігати за ним. Вікно знаходиться у фоновому режимі, але все ще може бути видимим.

Підсумки

Спливаючі вікна використовуються рідко, оскільки є альтернативи: завантаження та відображення інформації на сторінці або в iframe.

Якщо ми збираємося відкрити спливаюче вікно, хороша практика – повідомити про це користувача. Значок “відкриття вікна” поруч із посиланням або кнопкою дозволить відвідувачеві зрозуміти, що відбувається з фокусом і не втратити обидва вікна з поля зору.

  • Спливаюче вікно можна відкрити за допомогою виклику open(url, name, params). Він повертає посилання на щойно відкрите вікно.
  • Браузери блокують виклики open з коду за межами дій користувача. Зазвичай з’являється сповіщення, щоб користувач міг їх дозволити.
  • Типово браузери відкривають нову вкладку, але якщо вказано розміри, то це буде спливаюче вікно.
  • Спливаюче вікно може отримати доступ до основного вікна за допомогою властивості window.opener.
  • Основне вікно та спливаюче вікно можуть вільно читати та змінювати одне одного, якщо вони мають однакове походження. Інакше вони можуть змінити розташування один одного та обмінюватися повідомленнями.

Щоб закрити спливаюче вікно: скористайтеся викликом close(). Також користувач може закрити їх (як і будь-які інші вікна). Після цього window.closed стає true.

  • Методи focus() і blur() дозволяють фокусувати/розфокусувати вікно. Але вони працюють не завжди.
  • Події focus та blur дозволяють відстежувати фокусування на вікні та поза ним. Але зверніть увагу, що вікно все ще може бути видимим навіть у фоновому стані після blur.
Навчальна карта