DevGang
Авторизоваться

Как управлять DOM на чистом JavaScript?

Управление DOM с помощью JavaScript в современных браузерах и IE 11+

Добавить или удалить класс из элемента Разрешить ввод только определенных символов Добавить к элементу Подписаться на событие Вычислить положение мыши относительно элемента Рассчитать размер полосы прокрутки Изменить favicon Проверить элемент по селектору Проверка наличия класса у элемента Проверьте, является ли элемент потомком другого Проверьте, находится ли элемент в области просмотра Проверка, является ли элемент прокручиваемым Проверка, поддерживается ли ввод даты Клонировать элемент Связь между iframe и его родительским окном Скопировать текст в буфер обмена Подсчитайте количество символов текстовой области Создать элемент Создать слайдер сравнения изображений Создать одноразовый обработчик событий Создание изменяемых размеров разделенных видов Обнаружение кликов за пределами элемента Определить, находится ли элемент в фокусе Определите, включен ли caps-lock Как определить Mac OS браузер Определить высоту и ширину элемента Определение левый и правый клик мыши Скачать файл Перетаскивание элементов в списоке Перетаскивание столбецов таблицы Перетаскивание строки таблицы Выполнить код, когда документ готов Экспорт таблицы в CSV Получить CSS-стили элемента Получить или установить заголовок документа Получить или установить HTML-элемент Получить, установить и удалить атрибуты Получить установить и удалить атрибуты data Получить братьев и сестер элемента Получить размер выбранного файла Получить ближайший элемент по данному селектору Получить значение по умолчанию для свойства css Получиту высоту и ширину документа Получить первый прокручиваемый родительский элемент Получить родительский элемент Получить положение элемента относительно другого Получить позицию элемента относительно документа Получить размер изображения Получить текстовое содержимое элемента Вернуться на предыдущую страницу Выделите элемент при перетаскивании файла поверх него Вставить элемент после или перед другим элементом Вставить данный HTML после или перед элементом Загрузить файл CSS динамически Загрузить файл JavaScript динамически Цикл по нодлисту Сделать изменяемый размер элемента Вставить изображение из буфера обмена Placeholder для contenteditable Нажмите Shift и введите новую строку Предварительный просмотр изображения перед его загрузкой Поместить курсор в конец ввода Перенаправить на другую страницу Перезагрузить текущую страницу Удалить все дочерние узлы Удалить элемент Заменить элемент Заменить элемент Заменить сломанные изображения Измените размер фрейма, чтобы он соответствовал его содержанию Изменить размер изображения Изменить размер столбцов таблицы Измените ширину текстового поля, чтобы оно автоматически соответствовало его содержимому Масштабировать текст, чтобы он поместился внутри элемента Прокрутить до верхней части страницы Выберите элемент или список элементов Выберите дочерние элементы Сериализация данных формы в строку запроса Показывать фальшивый элемент при перетаскивании элемента Сортировка таблицы, нажав на ее заголовки Поменять местами два узла Скрыть / показать элемент Переключить видимость пароля Запустить событие Развернуть элемент Загрузить файлы с помощью AJAX Обернуть элемент вокруг данного элемента

В этом примере мы создадим сортируемый список, элементы которого можно перетаскивать в него:

<div id="list">
    <div class="draggable">A</div>
    <div class="draggable">B</div>
    <div class="draggable">C</div>
    <div class="draggable">D</div>
    <div class="draggable">E</div>
</div>

Каждый элемент имеет класс draggable, указывающий, что пользователь может перетащить его:

.draggable {
    cursor: move;
    user-select: none;
}

Сделать элементы перетаскиваемыми

Используя аналогичный подход, упомянутый в публикации Сделать перетаскиваемый элемент, мы можем превратить каждый элемент в перетаскиваемый элемент:

// The current dragging item
let draggingEle;

// The current position of mouse relative to the dragging element
let x = 0;
let y = 0;

const mouseDownHandler = function(e) {
    const draggingEle = e.target;

    // Calculate the mouse position
    const rect = draggingEle.getBoundingClientRect();
    x = e.pageX - rect.left;
    y = e.pageY - rect.top;

    // Attach the listeners to `document`
    document.addEventListener('mousemove', mouseMoveHandler);
    document.addEventListener('mouseup', mouseUpHandler);
};

const mouseMoveHandler = function(e) {
    // Set position for dragging element
    draggingEle.style.position = 'absolute';
    draggingEle.style.top = `${e.pageY - y}px`; 
    draggingEle.style.left = `${e.pageX - x}px`;
};

Обработчик события mouseup будет удалить стили позиционных перетаскиваний элемента и очищают обработчик событий:

const mouseUpHandler = function() {
    // Remove the position styles
    draggingEle.style.removeProperty('top');
    draggingEle.style.removeProperty('left');
    draggingEle.style.removeProperty('position');

    x = null;
    y = null;
    draggingEle = null;

    // Remove the handlers of `mousemove` and `mouseup`
    document.removeEventListener('mousemove', mouseMoveHandler);
    document.removeEventListener('mouseup', mouseUpHandler);
};

Теперь мы можем прикрепить событие mousedown к каждому элементу, просматривая список элементов:

// Query the list element
const list = document.getElementById('list');

// Query all items
[].slice.call(list.querySelectorAll('.draggable')).forEach(function(item) {
    item.addEventListener('mousedown', mouseDownHandler);
});

Добавить заполнитель

Давайте еще раз посмотрим на список предметов:

A
B
C
D
E

Например, когда мы перетаскиваем элемент C, следующий элемент (D) перемещается вверх и занимает область перетаскиваемого элемента (C). Чтобы это исправить, мы создаем динамический элемент-заполнитель и вставляем его прямо перед перетаскиваемым элементом. Высота заполнителя должна быть такой же, как у перетаскиваемого элемента.

Заполнитель создается один раз при перемещении мыши, поэтому мы добавляем новый флаг isDraggingStarted для его отслеживания:

let placeholder;
let isDraggingStarted = false;

const mouseMoveHandler = function(e) {
    const draggingRect = draggingEle.getBoundingClientRect();

    if (!isDraggingStarted) {
        // Update the flag
        isDraggingStarted = true;

        // Let the placeholder take the height of dragging element
        // So the next element won't move up
        placeholder = document.createElement('div');
        placeholder.classList.add('placeholder');
        draggingEle.parentNode.insertBefore(
            placeholder,
            draggingEle.nextSibling
        );

        // Set the placeholder's height
        placeholder.style.height = `${draggingRect.height}px`;
    }

    ...
}

Заполнитель будет удален, как только пользователи уронят элемент:

const mouseUpHandler = function() {
    // Remove the placeholder
    placeholder && placeholder.parentNode.removeChild(placeholder);

    ...
};

Вот порядок элементов, когда пользователь перетаскивает элемент:

A
B
placeholder   <- The dynamic placeholder
C             <- The dragging item
D
E

Определите, перемещает ли пользователь элемент вверх или вниз

Прежде всего, нам нужна вспомогательная функция, чтобы проверить, находится ли элемент выше или ниже другого.

nodeA обрабатывается, как указано выше, nodeB если горизонтальная центральная точка nodeA меньше, чем nodeB. Центральную точку узла можно рассчитать, взяв сумму его вершины и половины его высоты:

const isAbove = function(nodeA, nodeB) {
    // Get the bounding rectangle of nodes
    const rectA = nodeA.getBoundingClientRect();
    const rectB = nodeB.getBoundingClientRect();

    return (rectA.top + rectA.height / 2 < rectB.top + rectB.height / 2);
};

Когда пользователь перемещает элемент, мы определяем предыдущий и следующий элементы родственного элемента:

const mouseMoveHandler = function(e) {
    // The current order:
    // prevEle
    // draggingEle
    // placeholder
    // nextEle
    const prevEle = draggingEle.previousElementSibling;
    const nextEle = placeholder.nextElementSibling;    
};

Если пользователь переместит элемент в начало, мы поменяем местами заполнитель и предыдущий элемент:

const mouseMoveHandler = function(e) {
    ...

    // User moves item to the top
    if (prevEle && isAbove(draggingEle, prevEle)) {
        // The current order    -> The new order
        // prevEle              -> placeholder
        // draggingEle          -> draggingEle
        // placeholder          -> prevEle
        swap(placeholder, draggingEle);
        swap(placeholder, prevEle);
        return;
    }
};

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

const mouseMoveHandler = function(e) {
    ...

    // User moves the dragging element to the bottom
    if (nextEle && isAbove(nextEle, draggingEle)) {
        // The current order    -> The new order
        // draggingEle          -> nextEle
        // placeholder          -> placeholder
        // nextEle              -> draggingEle
        swap(nextEle, placeholder);
        swap(nextEle, draggingEle);
    }
};

Вот небольшая функция swap для замены двух узлов:

const swap = function(nodeA, nodeB) {
    const parentA = nodeA.parentNode;
    const siblingA = nodeA.nextSibling === nodeB ? nodeA : nodeA.nextSibling;

    // Move `nodeA` to before the `nodeB`
    nodeB.parentNode.insertBefore(nodeA, nodeB);

    // Move `nodeB` to before the sibling of `nodeA`
    parentA.insertBefore(nodeB, siblingA);
};
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться