Как реализовать перетаскивание модального окна мышкой на чистом JavaScript с использованием событий?
Что проверяют
Знаете ли вы модель событий указателя и умеете ли сделать перетаскивание, которое не теряет курсор, работает на тачскрине и не дёргает страницу. Следом обычно просят ограничить окно границами экрана или спрашивают, почему окно «прыгает» в начале движения.
Как отвечать
Используйте Pointer Events вместо пары mouse* и touch*: одни и те же обработчики работают для мыши, пальца и стилуса.
pointerdownна заголовке, а не на всём окне: иначе в окне нельзя выделить текст и кликнуть по полям. Запомните смещение курсора относительно угла окна как разницуe.clientXиrect.left, иначе окно прыгнет углом под курсор;setPointerCapture(e.pointerId): все последующиеpointermoveприходят на заголовок, даже если курсор быстро улетел за его пределы или оказался надiframe;pointermove: считаете новые координаты и ограничиваете их черезMath.minиMath.maxпоwindow.innerWidthиinnerHeight, чтобы заголовок не ушёл за край;pointerupиpointercancel: заканчиваете перетаскивание и снимаете флаг.pointercancelприходит, когда браузер забрал жест себе.
Двигать лучше через transform: translate(x, y), а не left и top: трансформация не вызывает перерасчёт раскладки и отрисовывается на композиторе. Частые события можно схлопнуть через requestAnimationFrame. В CSS заголовку нужен touch-action: none, чтобы палец не прокручивал страницу, а на время перетаскивания user-select: none.
Если окно построено на <dialog> и showModal(), оно лежит в top layer и центруется через margin: auto, поэтому центрирование отключают и задают позицию явно. Плюсом будет упомянуть сдвиг окна с клавиатуры.
Где спотыкаются
Вешают mousemove на само окно. При быстром движении курсор выходит за его пределы, события перестают приходить, и окно «отстаёт» от мыши.
Начинают перетаскивание с кнопки закрытия в заголовке. Нужна проверка e.target.closest('button'), иначе клик по крестику двигает окно.
Добавляют слушатели mousemove и mouseup на каждый mousedown анонимными функциями и не снимают их после отпускания. Через несколько перетаскиваний одно движение обрабатывает десяток копий обработчика.