> Как реализовать перетаскивание модального окна мышкой на чистом JavaScript с использованием событий? (JavaScript)
Уровень: senior · Роль: frontend · Категория: Технические вопросы
Компании: Cyber Interactive
Стек: JavaScript
> Пример ответа
Короткий ответ
Реализация перетаскивания модального окна на чистом JavaScript строится на обработке трёх событий мыши: mousedown на заголовке окна (начало перетаскивания), mousemove на document (обновление позиции) и mouseup на document (завершение). В mousedown запоминаем смещение курсора относительно окна, в mousemove вычисляем новую позицию через clientX/clientY минус смещение, в mouseup отписываемся от событий. Важно предотвращать выделение текста (user-select: none) и учитывать границы viewport.
Подробное объяснение
Ключевая идея - преобразование координат курсора в координаты окна через запоминание точки привязки. При mousedown на draggable-элементе (обычно заголовок) вычисляем offsetX = event.clientX - modal.offsetLeft и offsetY = event.clientY - modal.offsetTop. Затем на mousemove устанавливаем modal.style.left = event.clientX - offsetX + 'px' и top аналогично. Для предотвращения выхода за границы экрана добавляем проверки: Math.max(0, Math.min(maxX, newLeft)). Также нужно отключать стандартное поведение браузера (выделение, перетаскивание изображений) через event.preventDefault() и user-select: none на время перетаскивания.
На практике
Основные сценарии использования: кастомные диалоговые окна, панели инструментов, виджеты с произвольным позиционированием. Критично обрабатывать случай, когда пользователь быстро двигает мышь - события mousemove должны быть привязаны к document, а не к самому окну, иначе при выходе курсора за границы окна перетаскивание прервётся. Также нужно учитывать touch-события для мобильных устройств (аналогичная логика на touchstart/touchmove/touchend). Для производительности стоит использовать requestAnimationFrame при обновлении позиции, особенно если окно содержит тяжёлый контент.
Пример кода
JAVASCRIPTclass DraggableModal {constructor(modalElement, handleElement) {this.modal = modalElement;this.handle = handleElement;this.isDragging = false;this.offset = { x: 0, y: 0 };this.handle.addEventListener('mousedown', this.onMouseDown.bind(this));}onMouseDown(e) {if (e.target.closest('.modal-header')) {this.isDragging = true;this.offset.x = e.clientX - this.modal.offsetLeft;this.offset.y = e.clientY - this.modal.offsetTop;document.addEventListener('mousemove', this.onMouseMove);document.addEventListener('mouseup', this.onMouseUp);document.body.style.userSelect = 'none';e.preventDefault();}}onMouseMove = (e) => {if (!this.isDragging) return;const newLeft = e.clientX - this.offset.x;const newTop = e.clientY - this.offset.y;const maxLeft = window.innerWidth - this.modal.offsetWidth;const maxTop = window.innerHeight - this.modal.offsetHeight;this.modal.style.left = Math.max(0, Math.min(newLeft, maxLeft)) + 'px';this.modal.style.top = Math.max(0, Math.min(newTop, maxTop)) + 'px';}onMouseUp = () => {this.isDragging = false;document.removeEventListener('mousemove', this.onMouseMove);document.removeEventListener('mouseup', this.onMouseUp);document.body.style.userSelect = '';}}
Как отвечать на собеседовании
Начни с ключевой тройки событий и объяснения расчёта смещения. Затем добавь про граничные условия: привязка к document, предотвращение выделения, ограничение границами экрана. Упомяни touch-адаптацию и оптимизацию через requestAnimationFrame. Если спросят про альтернативы - можно упомянуть CSS resize или библиотеки, но подчеркни, что нативный JS даёт полный контроль. Хорошо показать понимание memory leaks (отписка от событий в mouseup).
Что проверяет интервьюер
- Понимание event propagation и разницы между событиями на элементе и document
- Умение работать с координатами и DOM-геометрией (offsetLeft, clientX)
- Навыки обработки граничных случаев (быстрое движение, выход за экран)
- Знание паттернов управления состоянием (флаг isDragging)
- Понимание производительности (лишние reflow, debounce/throttle)
Типичные ошибки
- Привязка
mousemoveк самому модальному окну вместо document - перетаскивание прерывается при выходе курсора - Забывают отписываться от событий в
mouseup- утечка памяти и баги - Не учитывают начальное смещение курсора - окно "перескакивает" в угол
- Игнорируют
user-select: none- выделяется текст при перетаскивании - Не ограничивают позицию границами viewport - окно уходит за экран
- Используют
getBoundingClientRectвместоoffsetLeftв mousedown - ошибка при CSS-трансформациях
> Похожие задачи по frontend
Какие данные остаются после преобразования типов в JavaScript и когда оно происходит
Если объект является экземпляром класса и методы лежат в прототипе, где их увидим: в for-in или Object.keys
Как реализовать бесконечную ленту (infinite scroll) в веб-приложении
Что представляет браузер и какие объекты он предоставляет
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью