Skip to content

Instantly share code, notes, and snippets.

@ipetropolsky
Last active August 29, 2015 14:03
Show Gist options
  • Save ipetropolsky/d1c7233d1911ae80a15f to your computer and use it in GitHub Desktop.
Save ipetropolsky/d1c7233d1911ae80a15f to your computer and use it in GitHub Desktop.
Позиционирование дропдауна

Позиционирование дропдауна

Проблема: дропдаун не меняет положения при ресайзе или изменении положения родителя. Касается также тултипов, поповеров и попапов.

Вариант №1: Кладём в body

Плюсы

  • Чистые стили, только reset.css.
  • Позиционирование независимо от overflow: hidden.

Минусы

  1. Кусок кода уезжает со своего места, порождая пачку проблем:
  • Если там инпуты, они отрываются от формы, нужно следить за их заполнением и дублировать в hidden-поля.
  • Если там подэлементы компонента, ломается поиск по JS-классам, ломается делегирование и слушание событий, нужно вводить специальные обёртки и кэшировать ссылки на них для каждого компонента, попавшего в дропдаун.
  • Даже при отсутствии подэлементов ломается обычная событийная работа, потому что события всплывают прямо в body, минуя всех родителей.
  • Невозможно поместить дропдаун в элемент с overflow: hidden с обрезанием, если это всё же понадобится. Пример: дропдаун в попапе.
  1. Дропдаун никак не связан со свитчером, что порождает ещё одну пачку проблем:
  • При изменении размеров окна свитчер может переехать, нужно обновлять его по событию resize (сделано).
  • При изменении положения свитчера (перед ним открылся ToggleBlock) нужно передвигать дропдаун, а это можно отследить только таймером.
  • При изменении видимости любого из родительских элементов свитчера дропдаун нужно скрыть или показать, тоже отслеживается только таймером.
  • Перемещение на JS по определению отстаёт и лагает, где-то больше, где-то меньше.
  1. (?) Перекрытие друг друга дропдаунами, тултипами, поповерами и попапами, которые произвольно насыпались в конец body. Перекрытие одного другим можно разрулить только через z-index, но дропдаун может открыться на странице под попапом и в самом попапе, в одном случае он должен быть под паранжой, в другом над паранжой.

Вариант №2: Кладём рядом со свитчером

Плюсы

  • Переезжает сам вместе со свитчером, пока тот неподвижен относительно своего offsetParent'a. Если обернуть свитчер в элемент с position: relative, дропдаун будет переезжать вместе с ним при любом сдвиге и ресайзе нативно и без лагов.
  • Скрывается и показывается вместе с элементом при изменении видимости родителей.
  • Элементы остаются на месте, находятся по дереву, события всплывают куда надо. Не требуется вмешательство в другие компоненты.

Минусы

  • Наследуются стили родителя, нужен сброс со стороны блока или специальная обёртка со стороны клиента. Вариант решения: g-reset.
  • Если свитчер внутри элемента с overflow: hidden, дропдаун будет обрезаться. Вариант решения: в редких случаях помещения дропдауна с компонентами внутри в overflow: hidden по параметру осознанно переводить его в режим добавления в body и рефакторить попавшие в него компоненты.

Как у других

Twitter, Google, Github кладут рядом. Яндекс кладёт в body и не следит за положением свитчера, можно подвинуть его стилями или поместив что-то перед ним, дропдаун остаётся на месте. При ресайзе на главной Яндекса дропдаун перерисовывается, а в картах нет.

См. скриншоты.

Что делать с попапом?

ХЗ. Проблемы те же, но выставить блок поверх всех остальных из любого места DOM не так-то просто.

@xnimorz
Copy link

xnimorz commented Jul 2, 2014

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

Яндекс же верстает при помощи БЭМ.

Из других отечественных разработчиков - vk кладет в конец body
mail.ru - зависит от проекта

@Splurov
Copy link

Splurov commented Jul 3, 2014

Я однозначно за второй вариант, но с оговорками: никаких g-reset и сбросов «всех» стилей на дропдауне — об этом должен заботиться разработчик в вызывающем коде. То же самое с overflow: hidden — об этом должен заботиться разработчик, и не нужно делать специальный вариант со складыванием в body.

@ipetropolsky
Copy link
Author

Я однозначно за второй вариант, но с оговорками: никаких g-reset и сбросов «всех» стилей на дропдауне — об этом должен заботиться разработчик в вызывающем коде.

Кто-нибудь приведёт аргументы против? А то я слышу только что это «ужастно и опастно» :) Объясните в чём проблема сброса наследуемых свойств (не всех).

То же самое с overflow: hidden — об этом должен заботиться разработчик, и не нужно делать специальный вариант со складыванием в body.

Как ты позаботишься об этом, если скрипт умеет только в body или только рядом? Вот если он умеет и туда, и сюда, ты можешь позаботиться и указать ему правильный вариант для конкретной ситуации (если он отличается от дефолтного).

Самая большая проблема второго способа в том, что если ты положил дропдаун рядом, уже ничего не сделаешь с overflow: hidden. Но это бывает не часто, и, если тебе не нужен доступ к элементам и событиям внутри, легко решается перемещением в body.

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

@ipetropolsky
Copy link
Author

кеширование и обертки вполне справляются с поставленными задачами, а дропдаун выполняет корректно свое назначение

Кэширование и обёртки требуют вмешательства в другие компоненты, дополнительного тестирования и т.п., и отбивают всякую охоту пользоваться компонентом, раз он не работает из коробки.

данный кейс возможен, только если существует js код, который без вмешательства пользователя элемент скроет, либо дропдаун настроен на мануальный режим управления - во втором случае все вопросы по управлению элементом относятся к разработчику

Дропдаун показывается на элементе внутри ToggleBlock'а, юзер закрывает его, дропдаун остаётся.

А переводить его осознанно в body -> это означает поддерживать два варианта + делать поиск по overflow: hidden по всему DOM.

Нет, переключать вручную параметром. Автоматика не поможет, потому что тогда доступ внутрь то будет, то не будет в зависимости от overflow: hidden на странице. Тут нужен рубильник.

@ipetropolsky
Copy link
Author

Лучше бы в почте обсудили, отсюда не приходит никаких уведомлений о комментах :)

@xnimorz
Copy link

xnimorz commented Jul 3, 2014

"Нет, переключать вручную параметром. Автоматика не поможет, потому что тогда доступ внутрь то будет, то не будет в зависимости от overflow: hidden на странице. Тут нужен рубильник."

Об этом я говорил на первой встрече с тобой и Димой.
Этот вариант был отброшен, так как мы получим два поведения dropdown, каждый из которых необходимо поддерживать.

"Дропдаун показывается на элементе внутри ToggleBlock'а, юзер закрывает его, дропдаун остаётся." - соответственно - для дропдауна включен ручной режим открытия\закрытия (иначе дропдаун закроется)

@ipetropolsky
Copy link
Author

Этот вариант был отброшен, так как мы получим два поведения dropdown, каждый из которых необходимо поддерживать.

if (this.params.placeToBody) {
    $parent = $(document.body);
    coords = $switcher.position();
} else {
    $parent = $switcher.parent();
    coords = $switcher.offset();
}

@ipetropolsky
Copy link
Author

"Дропдаун показывается на элементе внутри ToggleBlock'а, юзер закрывает его, дропдаун остаётся." - соответственно - для дропдауна включен ручной режим открытия\закрытия (иначе дропдаун закроется)

Я написал в начале, что проблема касается любых поповеров. То, что сейчас дропдаун закрывается при клике куда-либо и это нельзя отключить — очень конкретное (и странное) поведение, на которое нельзя завязываться. У тултипа, к примеру, для этого есть настройка.

Думай так: есть ли что-то необычное в том, чтобы оставить дропдаун открытым при клике на документе? Ничего необычного нет, и на Яндексе и других сайтах часто так бывает, значит и наш дропдаун может не закрыться. Ещё ToggleBlock может закрыться скриптом, и т.п., от того что ты переложишь заботу обо всём этом на разработчика, использующего дропдаун, им станет только сложнее пользоваться.

Если хочешь, чтобы он умел только примитивные базовые вещи, надо оставить только ручное открытие/закрытие и дополнить его какими-то менеджерами, которые будут следить за видимостью, положением, ресайзом, кликами, и делать всё что нужно для нормальной работы компонента. В этом тоже есть смысл: модуль дропдаун + тонкая управляющая обёртка, модуль можно использовать в других компонентах.

Это уже тонкости реализации, главное чтобы компонент работал из коробки и не создавал никаких проблем разработчику.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment