Глобальное переопределение курсора мыши с помощью JavaScript
В моем веб-приложении я пытаюсь реализовать некоторые функции перетаскивания. У меня есть глобальный компонент JavaScript, который делает основные вещи. Этот объект также отвечает за изменение курсора мыши в зависимости от текущей операции перетаскивания (перемещение, копирование, ссылка). На моей веб-странице есть различные элементы HTML, которые определяют собственный стиль курсора, либо встроенный, либо с помощью файла CSS.
Итак, есть ли способ для моего центрального компонента перетаскивания изменить курсор мыши глобально, независимо от стиля элемента, над которым находится курсор мыши?
Я старался:
document.body.style.cursor = "move"
а также
document.body.style.cursor = "move !important"
Но это не работает. Каждый раз, когда я перетаскиваю элемент, который определяет стиль курсора, курсор меняется на этот стиль.
Конечно, я мог бы изменить стиль элемента, который я сейчас перетаскиваю, но затем я должен сбросить его, когда перехожу к элементу. Это кажется немного сложным. Я ищу глобальное решение.
8 ответов
Пожалуйста: не убивайте свой CSS!
Чтобы реализовать функцию перетаскивания, вы должны использовать очень важный API: element.setCapture(). Это имеет следующие последствия:
- Все события мыши перенаправляются на целевой элемент захвата, независимо от того, где они произошли (даже за пределами окна браузера)
- Курсор будет курсором целевого элемента захвата, независимо от того, где находится указатель мыши.
Вы должны вызвать element.releaseCapture() или document.releaseCapture(), чтобы вернуться в нормальный режим в конце операции.
Остерегайтесь наивной реализации перетаскивания: у вас может быть много болезненных проблем, как, например (среди прочих): что произойдет, если мышь отпущена за пределы окна браузера или над элементом, у которого есть обработчик, который останавливает распространение, Использование setCapture() решает все эти проблемы, а также стиль курсора.
Вы можете прочитать этот превосходный учебник, который объясняет это подробно, если вы хотите реализовать перетаскивание самостоятельно.
Возможно, вы могли бы также использовать jQuery UI, если это возможно, в вашем контексте.
Я пробовал использовать
setPointerCapture
который отлично работал. Обратной стороной является то, что (естественно) все события указателя не будут работать, как раньше. Так что я потерял стили наведения и т. Д.
Мое решение теперь довольно простое и для моего случая использования лучше подходит, чем приведенные выше решения CSS.
Чтобы установить курсор, я добавляю новую таблицу стилей в заголовок:
const cursorStyle = document.createElement('style');
cursorStyle.innerHTML = '*{cursor: grabbing!important;}';
cursorStyle.id = 'cursor-style';
document.head.appendChild(cursorStyle);
Чтобы сбросить его, я просто удаляю таблицу стилей:
document.getElementById('cursor-style').remove();
document.body.style.cursor = "move"
должно работать просто отлично.
Тем не менее, я рекомендую делать глобальные стили с помощью CSS.
определить следующее:
body{
cursor:move;
}
Проблема в том, что определенные курсоры на других элементах переопределяют стиль тела.
Вы можете сделать что-то вроде этого:
your-element.style.cursor = "inherit"; // (or "default")
чтобы сбросить его до унаследованного стиля от тела или с помощью CSS:
body *{
cursor:inherit;
}
Обратите внимание, что *
обычно считается плохим выбором селектора.
К несчастью element.setCapture()
не работает для IE
Я использую метод грубой силы - открываю прозрачный div в верхней части всей страницы на время перетаскивания.
.tbFiller {
position:absolute;
z-index:5000;
left:0;
top:0;
width:100%;
height:100%;
background-color:transparent;
cursor:move;
}
...
function dragStart(event) {
// other code...
document.tbFiller=document.createElement("div");
document.tbFiller.className="tbFiller"
}
function dragStop(event) {
// other code...
document.tbFiller.parentNode.removeChild(document.tbFiller);
}
Благодаря некоторым другим ответам здесь на подсказки, это хорошо работает:
/* Disables all cursor overrides when body has this class. */
body.inheritCursors * {
cursor: inherit !important;
}
Примечание: мне не нужно было использовать<html>
а также document.documentElement
; вместо,<body>
а также document.body
работать нормально:
document.body.classList.add('inheritCursors');
Это приводит к тому, что все дочерние элементы<body>
(так как теперь это inheritCursors
class), чтобы унаследовать их курсор от <body>
сам, то есть то, что вы его установили:
document.body.style.cursor = 'progress';
Затем, чтобы вернуть управление дочерним элементам, просто удалите класс:
document.body.classList.remove('inheritCursors');
и убрать курсор с<body>
по умолчанию:
document.body.style.cursor = 'unset';
Если вы делаете декларацию CSS:
* {cursor:move;}
Он должен работать.
Это то, что я делаю, и это работает в Firefox, Chrome, Edge и IE с 2017 года.
Добавьте это правило CSS на свою страницу:
html.reset-all-cursors *
{
cursor: inherit !important;
}
Когда <html>
элемент имеет класс "reset-all-cursors", он переопределяет все курсоры, которые установлены для элементов индивидуально в их style
атрибут - без фактического манипулирования самими элементами. Нет необходимости убирать повсюду.
Затем, когда вы хотите переопределить курсор на всей странице с курсором любого element
эл. г. перетаскивая элемент, сделайте это в JavaScript:
element.setCapture && element.setCapture();
$("html").addClass("reset-all-cursors");
document.documentElement.style.setProperty("cursor", $(element).css("cursor"), "important");
Он использует setCapture
функция, где это доступно. В настоящее время это просто Firefox, хотя говорят, что это Microsoft API. Затем специальный класс добавляется ко всему документу, что отключает все пользовательские курсоры. Наконец установите курсор на документе, который вы хотите, чтобы он появлялся везде.
В сочетании с захватом событий это может даже расширить перетаскивающий курсор за пределы страницы и окна браузера. setCapture
делает это надежно в Firefox. Но в других местах он не работает каждый раз, в зависимости от браузера, его макета пользовательского интерфейса и пути, по которому курсор мыши покидает окно.;-)
Когда вы закончите, уберите:
element.releaseCapture && element.releaseCapture();
$("html").removeClass("reset-all-cursors");
document.documentElement.style.setProperty("cursor", "");
Это включает в себя JQuery для addClass
а также removeClass
, В простых сценариях вы можете просто сравнить и установить class
атрибут document.documentElement
, Это сломает другие библиотеки, такие как Modernizr. Вы можете избавиться от css
функция, если вы уже знаете желаемый курсор элемента, или попробуйте что-то вроде element.style.cursor
,
Приемлемое по производительности, но не идеальное решение, которое я в итоге использовал, состоит в том, чтобы изменитьcursor
prop элемента непосредственно под указателем, а затем возвращать его обратно в исходное состояние, когда указатель перемещается на другой элемент. Он работает сравнительно быстро, так как всего несколько элементов меняют свой стиль при перемещении указателя, но визуально вы иногда можете увидеть краткий проблеск «оригинального» курсора. Я считаю это гораздо более приемлемым компромиссом.
Итак, решение на TypeScript:
let prevElement: HTMLElement | undefined;
let prevElementOriginalCursor: string | undefined;
export const setTemporaryCursor = (element: HTMLElement, cursor: string | undefined) => {
// First, process the incoming element ASAP
let elementOriginalCursor: string | undefined;
requestAnimationFrame(() => {
if (element && prevElement !== element) {
elementOriginalCursor = element.style.cursor;
element.style.cursor = cursor ?? '';
}
});
// Then process the previous element, not so critical
requestAnimationFrame(() => {
if (prevElement && prevElement !== element) {
prevElement.style.cursor = prevElementOriginalCursor ?? '';
prevElementOriginalCursor = elementOriginalCursor;
}
prevElement = element;
});
};
export const resetTemporaryCursor = () => {
if (prevElement) {
prevElement.style.cursor = prevElementOriginalCursor ?? '';
prevElementOriginalCursor = undefined;
prevElement = undefined;
}
};
просто позвониsetTemporaryCursor
пока пользователь перемещает мышь и вызываетresetTemporaryCursor()
когда процесс перетаскивания завершен (наMouseUp
например).
Это делает работу для меня. ИспользованиеrequestAnimationFrame
является необязательным и, вероятно, может быть улучшен экспериментальным путем.