renovations
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
|
|
||||||
<div id="field">
|
<div id="field">
|
||||||
<img src="//js.cx/clipart/ball.gif" id="ball">
|
<img src="//js.cx/clipart/ball.svg" id="ball">
|
||||||
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ ball.style.top = Math.round(field.clientHeight/2 - ball.offsetHeight/2)+'px';
|
||||||
Код выше стабильно работать не будет, потому что `IMG` идет без ширины/высоты:
|
Код выше стабильно работать не будет, потому что `IMG` идет без ширины/высоты:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<img src="ball.gif" id="ball">
|
<img src="ball.svg" id="ball">
|
||||||
```
|
```
|
||||||
|
|
||||||
**Высота и ширина изображения неизвестны браузеру до тех пор, пока оно не загрузится, если размер не указан явно.**
|
**Высота и ширина изображения неизвестны браузеру до тех пор, пока оно не загрузится, если размер не указан явно.**
|
||||||
|
@ -43,7 +43,7 @@ ball.style.top = Math.round(field.clientHeight/2 - ball.offsetHeight/2)+'px';
|
||||||
Чтобы это исправить, добавим `width/height` к картинке:
|
Чтобы это исправить, добавим `width/height` к картинке:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<img src="ball.gif" *!*width="40" height="40"*/!* id="ball">
|
<img src="ball.svg" *!*width="40" height="40"*/!* id="ball">
|
||||||
```
|
```
|
||||||
|
|
||||||
Теперь браузер всегда знает ширину и высоту, так что все работает. Тот же эффект дало бы указание размеров в CSS.
|
Теперь браузер всегда знает ширину и высоту, так что все работает. Тот же эффект дало бы указание размеров в CSS.
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
|
|
||||||
<div id="field">
|
<div id="field">
|
||||||
<img src="//js.cx/clipart/ball.gif" width="40" height="40" id="ball">
|
<img src="//js.cx/clipart/ball.svg" width="40" height="40" id="ball">
|
||||||
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
|
|
||||||
<div id="field">
|
<div id="field">
|
||||||
<img src="//js.cx/clipart/ball.gif" id="ball">
|
<img src="//js.cx/clipart/ball.svg" id="ball">
|
||||||
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
|
|
||||||
|
|
||||||
<div id="field">
|
<div id="field">
|
||||||
<img src="//js.cx/clipart/ball.gif" id="ball">
|
<img src="//js.cx/clipart/ball.svg" id="ball">
|
||||||
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
|
|
||||||
|
|
||||||
<div id="field">
|
<div id="field">
|
||||||
<img src="//js.cx/clipart/ball.gif" id="ball">
|
<img src="//js.cx/clipart/ball.svg" id="ball">
|
||||||
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -4,16 +4,15 @@
|
||||||
|
|
||||||
В браузере нет способа "просто получить" текущие координаты. Это может сделать обработчик события, в данном случае `mousemove`. Поэтому нужно будет поставить обработчик на `mousemove` и при каждом движении запоминать текущие координаты, чтобы `setInterval` мог раз в 100мс сравнивать их.
|
В браузере нет способа "просто получить" текущие координаты. Это может сделать обработчик события, в данном случае `mousemove`. Поэтому нужно будет поставить обработчик на `mousemove` и при каждом движении запоминать текущие координаты, чтобы `setInterval` мог раз в 100мс сравнивать их.
|
||||||
|
|
||||||
Можно обойтись и без `setInterval` -- сравнивать координаты при каждом срабатывании `mousemove`. Если передвинулись на маленькое расстояние с последнего `mousemove` -- это "наведение на элемент", а на большое -- игнорируем. Вариант с `setInterval` теоретически надёжнее, но на практике и один `mousemove` работает.
|
Можно обойтись и без `setInterval` -- сравнивать координаты при каждом срабатывании `mousemove`. Если передвинулись на маленькое расстояние с последнего `mousemove` -- это "наведение на элемент", а на большое -- игнорируем. Вариант с `setInterval` лучше с точки зрения производительности -- `mousemove` происходит уж очень часто, но если проверка несложная, то и `mousemove` подойдёт.
|
||||||
|
|
||||||
Чтобы наш код не срабатывал чересчур часто, мы будем начинать анализ координат при заходе на элемент, а заканчивать -- при выходе с него.
|
Имеет смысл начинать анализ координат и отслеживание `mousemove` при заходе на элемент, а заканчивать -- при выходе с него.
|
||||||
|
|
||||||
Если выход осуществлён, и при этом на элементе зафиксировано "состояние наведения", то нужно вызвать соответствующий обработчик `options.out`.
|
|
||||||
|
|
||||||
Чтобы точно отловить момент входа и выхода, без учёта подэлементов (во избежание мигания), можно использовать `mouseenter/mouseleave`.
|
Чтобы точно отловить момент входа и выхода, без учёта подэлементов (во избежание мигания), можно использовать `mouseenter/mouseleave`.
|
||||||
|
|
||||||
В решении, предложенном ниже, однако, используется `mouseover/mouseout`, так как это позволит легко "прикрутить" к такому объекту делегирование, если потребуется. А, чтобы не было лишних срабатываний, лишние переходы отфильтровываются.
|
В решении, предложенном ниже, однако, используется `mouseover/mouseout`, так как это позволит легко "прикрутить" к такому объекту делегирование, если потребуется. А, чтобы не было лишних срабатываний, лишние переходы отфильтровываются.
|
||||||
|
|
||||||
|
При этом при обнаружении "наведения на элемент" это запоминается в переменной `isHover` и вызывается `options.over`, а затем, при выходе с элемента, если было "наведение", вызывается `options.out`.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,7 @@ new HoverIntent({
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
Демо:
|
Демо этого кода:
|
||||||
|
|
||||||
[iframe src="solution" height=110]
|
[iframe src="solution" height=110]
|
||||||
|
|
||||||
|
|
|
@ -20,16 +20,14 @@
|
||||||
|
|
||||||
Для `mouseout` всё наоборот:
|
Для `mouseout` всё наоборот:
|
||||||
|
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>`event.target` -- элемент, с которого ушла мышь, то есть на котором возникло событие.</li>
|
<li>`event.target` -- элемент, с которого ушла мышь, то есть на котором возникло событие.</li>
|
||||||
<li>`event.relatedTarget` -- элемент, на который перешла мышь.</li>
|
<li>`event.relatedTarget` -- элемент, на который перешла мышь.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
[online]
|
В примере ниже, если у вас есть мышь, вы можете наглядно посмотреть события `mouseover/out`, возникающие на всех элементах.
|
||||||
В примере ниже вы можете наглядно посмотреть события `mouseover/out`, возникающие на всех элементах.
|
|
||||||
|
|
||||||
[codetabs src="mouseoverout" height=220]
|
[codetabs src="mouseoverout" height=220]
|
||||||
[/online]
|
|
||||||
|
|
||||||
[warn header="`relatedTarget` может быть `null`"]
|
[warn header="`relatedTarget` может быть `null`"]
|
||||||
Свойство `relatedTarget` может быть равно `null`.
|
Свойство `relatedTarget` может быть равно `null`.
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
Как можно видеть из HTML/CSS, слайдер -- это `DIV`, подкрашенный фоном/градиентом, внутри которого находится другой `DIV`, оформленный как бегунок, с `position:relative`.
|
||||||
|
|
||||||
|
Бегунок немного поднят, и вылезает по высоте из родителя.
|
||||||
|
|
||||||
|
На этой основе мы реализуем горизонтальный Drag'n'Drop, ограниченный по ширине. Его особенность -- в `position:relative` у переносимого элемента, т.е. координата ставится не абсолютная, а относительно родителя.
|
|
@ -2,29 +2,7 @@
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<script src="lib.js"></script>
|
<link rel="stylesheet" href="style.css">
|
||||||
<style>
|
|
||||||
.slider {
|
|
||||||
border-radius: 5px;
|
|
||||||
background: #E0E0E0;
|
|
||||||
background: -moz-linear-gradient(left top , #E0E0E0, #EEEEEE) repeat scroll 0 0 transparent;
|
|
||||||
background: -webkit-gradient(linear, left top, right bottom, from(#E0E0E0), to(#EEEEEE));
|
|
||||||
background: linear-gradient(left top, #E0E0E0, #EEEEEE);
|
|
||||||
width: 310px;
|
|
||||||
height: 15px;
|
|
||||||
margin: 5px;
|
|
||||||
}
|
|
||||||
.thumb {
|
|
||||||
width: 10px;
|
|
||||||
height: 25px;
|
|
||||||
border-radius: 3px;
|
|
||||||
position: relative;
|
|
||||||
left: 10px;
|
|
||||||
top: -5px;
|
|
||||||
background: blue;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
@ -37,9 +15,7 @@
|
||||||
var sliderElem = document.getElementById('slider');
|
var sliderElem = document.getElementById('slider');
|
||||||
var thumbElem = sliderElem.children[0];
|
var thumbElem = sliderElem.children[0];
|
||||||
|
|
||||||
thumbElem.ondragstart = function() { return false; };
|
|
||||||
thumbElem.onmousedown = function(e) {
|
thumbElem.onmousedown = function(e) {
|
||||||
e = fixEvent(e);
|
|
||||||
var thumbCoords = getCoords(thumbElem);
|
var thumbCoords = getCoords(thumbElem);
|
||||||
var shiftX = e.pageX - thumbCoords.left;
|
var shiftX = e.pageX - thumbCoords.left;
|
||||||
// shiftY здесь не нужен, слайдер двигается только по горизонтали
|
// shiftY здесь не нужен, слайдер двигается только по горизонтали
|
||||||
|
@ -47,8 +23,6 @@ thumbElem.onmousedown = function(e) {
|
||||||
var sliderCoords = getCoords(sliderElem);
|
var sliderCoords = getCoords(sliderElem);
|
||||||
|
|
||||||
document.onmousemove = function(e) {
|
document.onmousemove = function(e) {
|
||||||
e = fixEvent(e);
|
|
||||||
|
|
||||||
// вычесть координату родителя, т.к. position: relative
|
// вычесть координату родителя, т.к. position: relative
|
||||||
var newLeft = e.pageX - shiftX - sliderCoords.left;
|
var newLeft = e.pageX - shiftX - sliderCoords.left;
|
||||||
|
|
||||||
|
@ -71,9 +45,21 @@ thumbElem.onmousedown = function(e) {
|
||||||
return false; // disable selection start (cursor change)
|
return false; // disable selection start (cursor change)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
thumbElem.ondragstart = function() {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getCoords(elem) { // кроме IE8-
|
||||||
|
var box = elem.getBoundingClientRect();
|
||||||
|
|
||||||
|
return {
|
||||||
|
top: box.top + pageYOffset,
|
||||||
|
left: box.left + pageXOffset
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
|
||||||
|
.slider {
|
||||||
|
border-radius: 5px;
|
||||||
|
background: #E0E0E0;
|
||||||
|
background: -moz-linear-gradient(left top , #E0E0E0, #EEEEEE) repeat scroll 0 0 transparent;
|
||||||
|
background: -webkit-gradient(linear, left top, right bottom, from(#E0E0E0), to(#EEEEEE));
|
||||||
|
background: linear-gradient(left top, #E0E0E0, #EEEEEE);
|
||||||
|
width: 310px;
|
||||||
|
height: 15px;
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumb {
|
||||||
|
width: 10px;
|
||||||
|
height: 25px;
|
||||||
|
border-radius: 3px;
|
||||||
|
position: relative;
|
||||||
|
left: 10px;
|
||||||
|
top: -5px;
|
||||||
|
background: blue;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="slider" class="slider">
|
||||||
|
<div class="thumb"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// ...Ваш код..
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,22 @@
|
||||||
|
|
||||||
|
.slider {
|
||||||
|
border-radius: 5px;
|
||||||
|
background: #E0E0E0;
|
||||||
|
background: -moz-linear-gradient(left top , #E0E0E0, #EEEEEE) repeat scroll 0 0 transparent;
|
||||||
|
background: -webkit-gradient(linear, left top, right bottom, from(#E0E0E0), to(#EEEEEE));
|
||||||
|
background: linear-gradient(left top, #E0E0E0, #EEEEEE);
|
||||||
|
width: 310px;
|
||||||
|
height: 15px;
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumb {
|
||||||
|
width: 10px;
|
||||||
|
height: 25px;
|
||||||
|
border-radius: 3px;
|
||||||
|
position: relative;
|
||||||
|
left: 10px;
|
||||||
|
top: -5px;
|
||||||
|
background: blue;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
|
@ -7,13 +7,8 @@
|
||||||
|
|
||||||
Захватите мышкой синий бегунок и двигайте его, чтобы увидеть в работе.
|
Захватите мышкой синий бегунок и двигайте его, чтобы увидеть в работе.
|
||||||
|
|
||||||
Позже к этому слайдеру можно будет добавить дополнительные функции по чтению/установке значения.
|
|
||||||
|
|
||||||
([getCoords](#getCoords) -- в lib.js).
|
|
||||||
|
|
||||||
Важно:
|
Важно:
|
||||||
<ul>
|
<ul>
|
||||||
<li>Слайдер должен нормально работать при резком движении мыши влево или вправо, за пределы полосы. При этом бегунок должен останавливаться четко в нужном конце полосы.</li>
|
<li>Слайдер должен нормально работать при резком движении мыши влево или вправо, за пределы полосы. При этом бегунок должен останавливаться четко в нужном конце полосы.</li>
|
||||||
<li>Курсор при передвижении слайдера должен быть рукой(`hand`) или крестиком(`move`).</li>
|
<li>При нажатом бегунке мышь может выходить за пределы полосы слайдера, но слайдер пусть все равно работает (это удобно для пользователя).</li>
|
||||||
<li>При нажатом бегунке мышь может выходить за пределы полосы слайдера, но слайдер пусть все равно работает (удобство для пользователя).</li>
|
|
||||||
</ul>
|
</ul>
|
|
@ -0,0 +1,5 @@
|
||||||
|
В решении этой задачи для переноса мы используем координаты относительно окна и `position:fixed`. Так проще.
|
||||||
|
|
||||||
|
А по окончании -- прибавляем прокрутку и делаем `position:absolute`, чтобы элемент был привязан к определённому месту в документе, а не в окне. Можно было и сразу `position:absolute` и оперировать в абсолютных координатах, но код был бы немного длиннее.
|
||||||
|
|
||||||
|
Детали решения расписаны в комментариях в исходном коде.
|
|
@ -8,9 +8,9 @@
|
||||||
|
|
||||||
<h2>Расставьте супергероев по полю.</h2>
|
<h2>Расставьте супергероев по полю.</h2>
|
||||||
|
|
||||||
<p>Супергерои -- это элементы с классом "draggable". Сделайте так, чтобы их можно было переносить.</p>
|
<p>Супергерои и мяч -- это элементы с классом "draggable". Сделайте так, чтобы их можно было переносить.</p>
|
||||||
|
|
||||||
<p>Важно: если супергероя подносят к низу или верху страницы, она должна автоматически прокручиваться. Конечно, можно прокрутить и клавиатурой, но так -- удобнее. Если страница помещается на вашем экране целиком и не имеет вертикальной прокрутки -- сделайте окно браузера меньше, чтобы протестировать эту возможность.</p>
|
<p>Важно: если супергероя подносят к низу или верху страницы, она должна автоматически прокручиваться. Если страница помещается на вашем экране целиком и не имеет вертикальной прокрутки -- сделайте окно браузера меньше, чтобы протестировать эту возможность.</p>
|
||||||
|
|
||||||
<p>Да, и ещё: супергерои ни при каких условиях не должны попасть за край экрана.</p>
|
<p>Да, и ещё: супергерои ни при каких условиях не должны попасть за край экрана.</p>
|
||||||
|
|
||||||
|
@ -23,15 +23,12 @@
|
||||||
<div class="hero draggable" id="hero3"></div>
|
<div class="hero draggable" id="hero3"></div>
|
||||||
<div class="hero draggable" id="hero4"></div>
|
<div class="hero draggable" id="hero4"></div>
|
||||||
<div class="hero draggable" id="hero5"></div>
|
<div class="hero draggable" id="hero5"></div>
|
||||||
<div id="winnie" class="draggable"></div>
|
<div class="hero draggable" id="hero6"></div>
|
||||||
|
|
||||||
<img src="//js.cx/drag-heroes/ball.png" class="draggable">
|
<img src="//js.cx/drag-heroes/ball.png" class="draggable">
|
||||||
|
|
||||||
<div style="clear:both"></div>
|
<div style="clear:both"></div>
|
||||||
|
|
||||||
<script src="//js.cx/libs/getCoords.js"></script>
|
|
||||||
<script src="//js.cx/libs/documentScroll.js"></script>
|
|
||||||
|
|
||||||
<script src="soccer.js"></script>
|
<script src="soccer.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
|
@ -10,9 +10,10 @@ html, body {
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* герои и мяч (переносимые элементы) */
|
||||||
.hero {
|
.hero {
|
||||||
background: url(//js.cx/drag-heroes/heroes.png);
|
background: url(//js.cx/drag-heroes/heroes.png);
|
||||||
width: 105px;
|
width: 130px;
|
||||||
height: 128px;
|
height: 128px;
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
@ -26,33 +27,21 @@ html, body {
|
||||||
}
|
}
|
||||||
|
|
||||||
#hero3 {
|
#hero3 {
|
||||||
background-position: -131px 0;
|
background-position: -120px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#hero4 {
|
#hero4 {
|
||||||
background-position: -131px -128px;
|
background-position: -125px -128px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#hero5 {
|
#hero5 {
|
||||||
background-position: -236px 0;
|
background-position: -248px -128px;
|
||||||
width: 130px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#winnie {
|
#hero6 {
|
||||||
background: url(//js.cx/drag-heroes/winnie.png);
|
background-position: -244px 0;
|
||||||
width: 115px;
|
|
||||||
height: 128px;
|
|
||||||
float: left;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.draggable {
|
.draggable {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dragging {
|
|
||||||
z-index: 1000;
|
|
||||||
position: absolute;
|
|
||||||
cursor: move;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,103 @@
|
||||||
|
|
||||||
|
|
||||||
|
document.onmousedown = function(e) {
|
||||||
|
|
||||||
|
var dragElement = e.target;
|
||||||
|
|
||||||
|
if (!dragElement.classList.contains('draggable')) return;
|
||||||
|
|
||||||
|
var coords, shiftX, shiftY;
|
||||||
|
|
||||||
|
startDrag(e.clientX, e.clientY);
|
||||||
|
|
||||||
|
document.onmousemove = function(e) {
|
||||||
|
moveAt(e.clientX, e.clientY);
|
||||||
|
};
|
||||||
|
|
||||||
|
dragElement.onmouseup = function() {
|
||||||
|
finishDrag();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// -------------------------
|
||||||
|
|
||||||
|
function startDrag(clientX, clientY) {
|
||||||
|
|
||||||
|
shiftX = clientX - dragElement.getBoundingClientRect().left;
|
||||||
|
shiftY = clientY - dragElement.getBoundingClientRect().top;
|
||||||
|
|
||||||
|
dragElement.style.position = 'fixed';
|
||||||
|
|
||||||
|
document.body.appendChild(dragElement);
|
||||||
|
|
||||||
|
moveAt(clientX, clientY);
|
||||||
|
};
|
||||||
|
|
||||||
|
function finishDrag() {
|
||||||
|
// конец переноса, перейти от fixed к absolute-координатам
|
||||||
|
dragElement.style.top = parseInt(dragElement.style.top) + pageYOffset + 'px';
|
||||||
|
dragElement.style.position = 'absolute';
|
||||||
|
|
||||||
|
document.onmousemove = null;
|
||||||
|
dragElement.onmouseup = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveAt(clientX, clientY) {
|
||||||
|
// новые координаты
|
||||||
|
var newX = clientX - shiftX;
|
||||||
|
var newY = clientY - shiftY;
|
||||||
|
|
||||||
|
// ------- обработаем вынос за нижнюю границу окна ------
|
||||||
|
// новая нижняя граница элемента
|
||||||
|
var newBottom = newY + dragElement.offsetHeight;
|
||||||
|
|
||||||
|
// если новая нижняя граница вылезает вовне окна - проскроллим его
|
||||||
|
if (newBottom > document.documentElement.clientHeight) {
|
||||||
|
// координата нижней границы документа относительно окна
|
||||||
|
var docBottom = document.documentElement.getBoundingClientRect().bottom;
|
||||||
|
|
||||||
|
// scrollBy, если его не ограничить - может заскроллить за текущую границу документа
|
||||||
|
// обычно скроллим на 10px
|
||||||
|
// но если расстояние от newBottom до docBottom меньше, то меньше
|
||||||
|
var scrollY = Math.min(docBottom - newBottom, 10);
|
||||||
|
|
||||||
|
// ошибки округления при полностью прокрученной странице
|
||||||
|
// могут привести к отрицательному scrollY, что будет означать прокрутку вверх
|
||||||
|
// поправим эту ошибку
|
||||||
|
if (scrollY < 0) scrollY = 0;
|
||||||
|
|
||||||
|
window.scrollBy(0, scrollY);
|
||||||
|
|
||||||
|
// резким движением мыши элемент можно сдвинуть сильно вниз
|
||||||
|
// если он вышел за нижнюю границу документа -
|
||||||
|
// передвигаем на максимально возможную нижнюю позицию внутри документа
|
||||||
|
newY = Math.min(newY, document.documentElement.clientHeight - dragElement.offsetHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ------- обработаем вынос за верхнюю границу окна ------
|
||||||
|
if (newY < 0) {
|
||||||
|
// проскроллим вверх на 10px, либо меньше, если мы и так в самом верху
|
||||||
|
var scrollY = Math.min(-newY, 10);
|
||||||
|
if (scrollY < 0) scrollY = 0; // поправим ошибку округления
|
||||||
|
|
||||||
|
window.scrollBy(0, -scrollY);
|
||||||
|
// при резком движении мыши элемент мог "вылететь" сильно вверх, поправим его
|
||||||
|
newY = Math.max(newY, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// зажать в границах экрана по горизонтали
|
||||||
|
// здесь прокрутки нет, всё просто
|
||||||
|
if (newX < 0) newX = 0;
|
||||||
|
if (newX > document.documentElement.clientWidth - dragElement.offsetHeight) {
|
||||||
|
newX = document.documentElement.clientWidth - dragElement.offsetHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
dragElement.style.left = newX + 'px';
|
||||||
|
dragElement.style.top = newY + 'px';
|
||||||
|
}
|
||||||
|
|
||||||
|
// отменим действие по умолчанию на mousedown (выделение текста, оно лишнее)
|
||||||
|
return false;
|
||||||
|
}
|
|
@ -8,13 +8,12 @@
|
||||||
|
|
||||||
<h2>Расставьте супергероев по полю.</h2>
|
<h2>Расставьте супергероев по полю.</h2>
|
||||||
|
|
||||||
<p>Супергерои -- это элементы с классом "draggable". Сделайте так, чтобы их можно было переносить.</p>
|
<p>Супергерои и мяч -- это элементы с классом "draggable". Сделайте так, чтобы их можно было переносить.</p>
|
||||||
|
|
||||||
<p>Важно: если супергероя подносят к низу или верху страницы, она должна автоматически прокручиваться. Конечно, можно прокрутить и клавиатурой, но так -- удобнее. Если страница помещается на вашем экране целиком и не имеет вертикальной прокрутки -- сделайте окно браузера меньше, чтобы протестировать эту возможность.</p>
|
<p>Важно: если супергероя подносят к низу или верху страницы, она должна автоматически прокручиваться. Если страница помещается на вашем экране целиком и не имеет вертикальной прокрутки -- сделайте окно браузера меньше, чтобы протестировать эту возможность.</p>
|
||||||
|
|
||||||
<p>Да, и ещё: супергерои ни при каких условиях не должны попасть за край экрана.</p>
|
<p>Да, и ещё: супергерои ни при каких условиях не должны попасть за край экрана.</p>
|
||||||
|
|
||||||
|
|
||||||
<div id="field">
|
<div id="field">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -24,15 +23,12 @@
|
||||||
<div class="hero draggable" id="hero3"></div>
|
<div class="hero draggable" id="hero3"></div>
|
||||||
<div class="hero draggable" id="hero4"></div>
|
<div class="hero draggable" id="hero4"></div>
|
||||||
<div class="hero draggable" id="hero5"></div>
|
<div class="hero draggable" id="hero5"></div>
|
||||||
<div id="winnie" class="draggable"></div>
|
<div class="hero draggable" id="hero6"></div>
|
||||||
|
|
||||||
<img src="//js.cx/drag-heroes/ball.png" class="draggable">
|
<img src="//js.cx/drag-heroes/ball.png" class="draggable">
|
||||||
|
|
||||||
<div style="clear:both"></div>
|
<div style="clear:both"></div>
|
||||||
|
|
||||||
<script src="//js.cx/libs/getCoords.js"></script>
|
|
||||||
<script src="//js.cx/libs/documentScroll.js"></script>
|
|
||||||
|
|
||||||
<script src="soccer.js"></script>
|
<script src="soccer.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
|
@ -10,9 +10,10 @@ html, body {
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* герои и мяч (переносимые элементы) */
|
||||||
.hero {
|
.hero {
|
||||||
background: url(//js.cx/drag-heroes/heroes.png);
|
background: url(//js.cx/drag-heroes/heroes.png);
|
||||||
width: 105px;
|
width: 130px;
|
||||||
height: 128px;
|
height: 128px;
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
@ -26,33 +27,21 @@ html, body {
|
||||||
}
|
}
|
||||||
|
|
||||||
#hero3 {
|
#hero3 {
|
||||||
background-position: -131px 0;
|
background-position: -120px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#hero4 {
|
#hero4 {
|
||||||
background-position: -131px -128px;
|
background-position: -125px -128px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#hero5 {
|
#hero5 {
|
||||||
background-position: -236px 0;
|
background-position: -248px -128px;
|
||||||
width: 130px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#winnie {
|
#hero6 {
|
||||||
background: url(//js.cx/drag-heroes/winnie.png);
|
background-position: -244px 0;
|
||||||
width: 115px;
|
|
||||||
height: 128px;
|
|
||||||
float: left;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.draggable {
|
.draggable {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dragging {
|
|
||||||
z-index: 1000;
|
|
||||||
position: absolute;
|
|
||||||
cursor: move;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
// Ваш код
|
20
2-ui/3-event-details/4-drag-and-drop/2-drag-heroes/task.md
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# Расставить супергероев по полю
|
||||||
|
|
||||||
|
[importance 5]
|
||||||
|
|
||||||
|
В этой задаче вы можете проверить своё понимание сразу нескольких аспектов Drag'n'Drop.
|
||||||
|
|
||||||
|
Сделайте так, чтобы элементы с классом `draggable` можно было переносить мышкой. По окончании переноса элемент остаётся на том месте в документе, где его положили.
|
||||||
|
|
||||||
|
Требования к реализации:
|
||||||
|
<ul>
|
||||||
|
<li>Должен быть 1 обработчик на `document`, использующий делегирование.</li>
|
||||||
|
<li>Если элементы подносят к вертикальным краям окна -- оно должно прокручиваться вниз/вверх.</li>
|
||||||
|
<li>Горизонтальной прокрутки в этой задаче не существует.</li>
|
||||||
|
<li>Элемент при переносе, даже при резких движениях мышкой, не должен попасть вне окна.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
Футбольное поле в этой задач слишком большое, чтобы показывать его здесь, поэтому откройте его, кликнув по ссылке ниже. Там же и подробное описание задачи (осторожно, винни-пух и супергерои!).
|
||||||
|
|
||||||
|
[demo src="solution"]
|
||||||
|
|
|
@ -5,48 +5,51 @@ Drag'n'Drop -- это возможность захватить мышью эл
|
||||||
Перенос мышкой может заменить целую последовательность кликов. И, самое главное, он упрощает внешний вид интерфейса: функции, реализуемые через Drag'n'Drop, в ином случае потребовали бы дополнительных полей, виджетов и т.п.
|
Перенос мышкой может заменить целую последовательность кликов. И, самое главное, он упрощает внешний вид интерфейса: функции, реализуемые через Drag'n'Drop, в ином случае потребовали бы дополнительных полей, виджетов и т.п.
|
||||||
|
|
||||||
[cut]
|
[cut]
|
||||||
|
|
||||||
## Отличия от HTML5 Drag'n'Drop
|
## Отличия от HTML5 Drag'n'Drop
|
||||||
|
|
||||||
В современном стандарте HTML5 есть поддержка Drag'n'Drop при помощи [специальных событий](http://www.html5rocks.com/en/tutorials/dnd/basics/). Эти события поддерживаются всеми браузерами, в мелочах отстаёт от них IE.
|
В современном стандарте HTML5 есть поддержка Drag'n'Drop при помощи [специальных событий](http://www.html5rocks.com/en/tutorials/dnd/basics/).
|
||||||
|
|
||||||
У них есть своя область применения, например можно перетащить файл в браузер, но здесь сосредоточимся на реализации техник Drag'n'Drop в более широком смысле, для более обширного класса задач.
|
Эти события поддерживаются всеми современными браузерами, и у них есть свои интересные особенности, например, можно перетащить файл в браузер, так что JS получит доступ к его содержимому. Они заслуживают отдельного рассмотрения.
|
||||||
|
|
||||||
**Далее речь пойдет о реализации Drag'n'Drop при помощи событий мыши.**
|
Но в плане именно Drag'n'Drop у них есть существенные ограничения. Например, нельзя организовать перенос "только по горизонтали" или "только по вертикали". Также нельзя ограничить перенос внутри заданной зоны. Есть и другие интерфейсные задачи, которые такими встроенными событиями нереализуемы.
|
||||||
|
|
||||||
**Изложенные методы применяются в элементах управления для обработки любых действий вида "захватить - потянуть - отпустить".**
|
Поэтому здесь мы будем рассматривать Drag'n'Drop при помощи событий мыши.
|
||||||
|
|
||||||
## Основная логика Drag'n'Drop
|
Рассматриваемые приёмы, вообще говоря, применяются не только в Drag'n'Drop, но и для любых интерфейсных взаимодействий вида "захватить - потянуть - отпустить".
|
||||||
|
|
||||||
Для организации Drag'n'Drop нужно:
|
## Алгоритм Drag'n'Drop
|
||||||
|
|
||||||
|
Основной алгоритм Drag'n'Drop выглядит так:
|
||||||
<ol>
|
<ol>
|
||||||
<li>При помощи события `mousedown` отследить нажатие кнопки на переносимом элементе.</li>
|
<li>Отслеживаем нажатие кнопки мыши на переносимом элементе при помощи события `mousedown`.</li>
|
||||||
<li>При нажатии -- подготовить элемент к перемещению: обычно ему назначается `position:absolute` и ставятся координаты `left/top` по координатам курсора.</li>
|
<li>При нажатии -- подготовить элемент к перемещению.</li>
|
||||||
<li>Далее отслеживаем движение мыши через <code>mousemove</code> и передвигаем переносимый элемент на новые координаты путём смены `left/top`.</li>
|
<li>Далее отслеживаем движение мыши через <code>mousemove</code> и передвигаем переносимый элемент на новые координаты путём смены `left/top` и `position:absolute`.</li>
|
||||||
<li>При отпускании кнопки мыши, то есть наступлении события <code>mouseup</code> -- остановить перенос элемента и произвести все действия, связанные с окончанием Drag'n'Drop.</li>
|
<li>При отпускании кнопки мыши, то есть наступлении события <code>mouseup</code> -- остановить перенос элемента и произвести все действия, связанные с окончанием Drag'n'Drop.</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
|
|
||||||
В следующем примере эти шаги реализованы для переноса мяча:
|
В следующем примере эти шаги реализованы для переноса мяча:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ autorun
|
|
||||||
var ball = document.getElementById('ball');
|
var ball = document.getElementById('ball');
|
||||||
|
|
||||||
ball.onmousedown = function(e) { // 1. отследить нажатие*!*
|
ball.onmousedown = function(e) { // 1. отследить нажатие*!*
|
||||||
var self = this;
|
|
||||||
|
|
||||||
// подготовить к перемещению
|
// подготовить к перемещению
|
||||||
// 2. разместить на том же месте, но в абсолютных координатах*!*
|
// 2. разместить на том же месте, но в абсолютных координатах*!*
|
||||||
this.style.position = 'absolute';
|
ball.style.position = 'absolute';
|
||||||
moveAt(e);
|
moveAt(e);
|
||||||
// переместим в body, чтобы мяч был точно не внутри position:relative
|
// переместим в body, чтобы мяч был точно не внутри position:relative
|
||||||
document.body.appendChild(this);
|
document.body.appendChild(ball);
|
||||||
|
|
||||||
this.style.zIndex = 1000; // показывать мяч над другими элементами
|
ball.style.zIndex = 1000; // показывать мяч над другими элементами
|
||||||
|
|
||||||
// передвинуть мяч под координаты курсора
|
// передвинуть мяч под координаты курсора
|
||||||
|
// и сдвинуть на половину ширины/высоты для центрирования
|
||||||
function moveAt(e) {
|
function moveAt(e) {
|
||||||
self.style.left = e.pageX-20+'px'; // 20 - половина ширины/высоты мяча
|
ball.style.left = e.pageX - ball.offsetWidth/2 + 'px';
|
||||||
self.style.top = e.pageY-20+'px';
|
ball.style.top = e.pageY - ball.offsetHeight/2 + 'px';
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3, перемещать по экрану*!*
|
// 3, перемещать по экрану*!*
|
||||||
|
@ -55,27 +58,24 @@ ball.onmousedown = function(e) { // 1. отследить нажатие*!*
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. отследить окончание переноса *!*
|
// 4. отследить окончание переноса *!*
|
||||||
this.onmouseup = function() {
|
ball.onmouseup = function() {
|
||||||
document.onmousemove = self.onmouseup = null;
|
document.onmousemove = null;
|
||||||
|
ball.onmouseup = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
В действии:
|
Если запустить этот код на картинке `#ball`, то мы заметим нечто странное. При начале переноса мяч "раздваивается" и переносится не сам мяч, а его "клон".
|
||||||
<div style="height:80px">
|
|
||||||
Кликните по мячу и тяните, чтобы двигать его.
|
|
||||||
<img src="//js.cx/clipart/ball.gif" style="cursor:pointer" width="40" height="40" id="ball">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
**Попробуйте этот пример. Он не совсем работает, мячик "раздваивается".**
|
[online]
|
||||||
|
В действии (внутри ифрейма):
|
||||||
|
|
||||||
Сейчас мы это исправим.
|
[iframe src="ball" height=230]
|
||||||
|
[/online]
|
||||||
|
|
||||||
## Отмена переноса браузера
|
Это потому, что браузер имеет свой собственный Drag'n'Drop, который автоматически запускается и вступает в конфликт с нашим. Это происходит именно для картинок и некоторых других элементов.
|
||||||
|
|
||||||
При нажатии мышью на `<img>` браузер начинает выполнять свой собственный, встроенный Drag'n'Drop, который и портит наш перенос.
|
Его нужно отключить:
|
||||||
|
|
||||||
Чтобы браузер не вмешивался, нужно отменить действие по умолчанию для события `dragstart`:
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
ball.ondragstart = function() {
|
ball.ondragstart = function() {
|
||||||
|
@ -83,78 +83,45 @@ ball.ondragstart = function() {
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
Исправленный пример:
|
Теперь всё будет в порядке.
|
||||||
|
|
||||||
```js
|
[online]
|
||||||
//+ autorun
|
В действии (внутри ифрейма):
|
||||||
var ball = document.getElementById('ball2');
|
|
||||||
|
|
||||||
ball.onmousedown = function(e) {
|
[iframe src="ball2" height=230]
|
||||||
var self = this;
|
[/online]
|
||||||
|
|
||||||
this.style.position = 'absolute';
|
Ещё одна особенность правильного Drag'd'Drop -- событие `mousemove` отслеживается на `document`, а не на `ball`.
|
||||||
moveAt(e);
|
|
||||||
document.body.appendChild(this);
|
|
||||||
|
|
||||||
this.style.zIndex = 1000;
|
|
||||||
|
|
||||||
function moveAt(e) {
|
|
||||||
self.style.left = e.pageX-20+'px';
|
|
||||||
self.style.top = e.pageY-20+'px';
|
|
||||||
}
|
|
||||||
|
|
||||||
document.onmousemove = function(e) {
|
|
||||||
moveAt(e);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.onmouseup = function() {
|
|
||||||
document.onmousemove = self.onmouseup = null;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
*!*
|
|
||||||
ball.ondragstart = function() {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
*/!*
|
|
||||||
```
|
|
||||||
|
|
||||||
В действии:
|
|
||||||
|
|
||||||
<div style="height:80px">
|
|
||||||
Кликните по мячу и тяните, чтобы двигать его.
|
|
||||||
<img src="//js.cx/clipart/ball.gif" style="cursor:pointer" width="40" height="40" id="ball2">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
[smart header="Обработчик `mousemove` ставим на `document`"]
|
|
||||||
|
|
||||||
**Почему событие `mousemove` в примере отслеживается на `document`, а не на `ball`?**
|
|
||||||
|
|
||||||
С первого взгляда кажется, что мышь всегда над мячом и обработчик `mousemove` можно повесить на сам мяч, а не на документ.
|
С первого взгляда кажется, что мышь всегда над мячом и обработчик `mousemove` можно повесить на сам мяч, а не на документ.
|
||||||
|
|
||||||
Однако, на самом деле **мышь во время переноса не всегда над мячом**. Вспомните, браузер регистрирует `mousemove` часто, но не для каждого пикселя.
|
Однако, на самом деле мышь во время переноса не всегда над мячом.
|
||||||
|
|
||||||
|
Вспомним, событие `mousemove` возникает хоть и часто, но не для каждого пикселя. Быстрое движение курсора вызовет `mousemove` уже не над мячом, а, например, в дальнем конце страницы.
|
||||||
|
|
||||||
Быстрое движение курсора вызовет `mousemove` уже не над мячом, а, например, в дальнем конце страницы.
|
|
||||||
Вот почему мы должны отслеживать `mousemove` на всём `document`.
|
Вот почему мы должны отслеживать `mousemove` на всём `document`.
|
||||||
[/smart]
|
|
||||||
|
|
||||||
## Правильное позиционирование
|
## Правильное позиционирование
|
||||||
|
|
||||||
В примерах выше мяч позиционируется в центре под курсором мыши:
|
В примерах выше мяч позиционируется в центре под курсором мыши:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
self.style.left = e.pageX - 20 + 'px';
|
self.style.left = e.pageX - ball.offsetWidth/2 + 'px';
|
||||||
self.style.top = e.pageY - 20 + 'px';
|
self.style.top = e.pageY - ball.offsetHeight/2 + 'px';
|
||||||
```
|
```
|
||||||
|
|
||||||
Число `20` здесь -- половина длины мячика. Оно использовано здесь потому, что если поставить `left/top` ровно в `pageX/pageY`, то мячик прилипнет верхним-левым углом к курсору мыши. Будет некрасиво.
|
Если поставить `left/top` ровно в `pageX/pageY`, то мячик прилипнет верхним-левым углом к курсору мыши. Будет некрасиво. Поэтому мы сдвигаем его на половину высоты/ширины, чтобы был центром под мышью. Уже лучше.
|
||||||
|
|
||||||
**Для правильного переноса необходимо, чтобы изначальный сдвиг курсора относительно элемента сохранялся: где захватили, за ту "часть элемента" и переносим.**
|
Но не идеально. В частности, в самом начале переноса, особенно если мячик "взят" за край -- он резко "прыгает" центром под курсор мыши.
|
||||||
|
|
||||||
|
**Для правильного переноса необходимо, чтобы изначальный сдвиг курсора относительно элемента сохранялся.**
|
||||||
|
|
||||||
|
Где захватили, за ту "часть элемента" и переносим:
|
||||||
|
|
||||||
<img src="ball_shift.png">
|
<img src="ball_shift.png">
|
||||||
|
|
||||||
<ul>
|
<ol>
|
||||||
<li>Когда человек нажимает на мячик `mousedown` -- курсор сдвинут относительно левого-верхнего угла мяча на расстояние `shiftX/shiftY`. И мы хотели бы сохранить этот сдвиг.
|
<li>Когда человек нажимает на мячик `mousedown` -- курсор сдвинут относительно левого-верхнего угла мяча на расстояние, которое мы обозначим `shiftX/shiftY`. И нужно при переносе сохранить этот сдвиг.
|
||||||
|
|
||||||
Получить значения `shiftX/shiftY` легко: достаточно вычесть из координат курсора `pageX/pageY` левую-верхнюю границу мячика, полученную при помощи функции [getCoords](#getCoords).
|
Получить значения `shiftX/shiftY` легко: достаточно вычесть из координат курсора `pageX/pageY` левую-верхнюю границу мячика, полученную при помощи функции [getCoords](#getCoords).
|
||||||
|
|
||||||
|
@ -173,48 +140,45 @@ shiftY = e.pageY - getCoords(ball).top;
|
||||||
|
|
||||||
```js
|
```js
|
||||||
// onmousemove
|
// onmousemove
|
||||||
self.style.left = e.pageX - *!*shiftX*/!* + 'px';
|
ball.style.left = e.pageX - *!*shiftX*/!* + 'px';
|
||||||
self.style.top = e.pageY - *!*shiftY*/!* + 'px';
|
ball.style.top = e.pageY - *!*shiftY*/!* + 'px';
|
||||||
```
|
```
|
||||||
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ol>
|
||||||
|
|
||||||
|
|
||||||
**Пример с правильным позиционированием:**
|
Итоговый код с правильным позиционированием:
|
||||||
|
|
||||||
В этом примере позиционирование осуществляется не на `20px`, а с учётом изначального сдвига.
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ autorun
|
var ball = document.getElementById('ball');
|
||||||
var ball = document.getElementById('ball3');
|
|
||||||
|
|
||||||
ball.onmousedown = function(e) {
|
ball.onmousedown = function(e) {
|
||||||
var self = this;
|
|
||||||
|
|
||||||
var coords = getCoords(this);
|
var coords = getCoords(ball);
|
||||||
*!*
|
*!*
|
||||||
var shiftX = e.pageX - coords.left;
|
var shiftX = e.pageX - coords.left;
|
||||||
var shiftY = e.pageY - coords.top;
|
var shiftY = e.pageY - coords.top;
|
||||||
*/!*
|
*/!*
|
||||||
|
|
||||||
this.style.position = 'absolute';
|
ball.style.position = 'absolute';
|
||||||
document.body.appendChild(this);
|
document.body.appendChild(ball);
|
||||||
moveAt(e);
|
moveAt(e);
|
||||||
|
|
||||||
this.style.zIndex = 1000; // над другими элементами
|
ball.style.zIndex = 1000; // над другими элементами
|
||||||
|
|
||||||
function moveAt(e) {
|
function moveAt(e) {
|
||||||
self.style.left = e.pageX - *!*shiftX*/!* + 'px';
|
ball.style.left = e.pageX - *!*shiftX*/!* + 'px';
|
||||||
self.style.top = e.pageY - *!*shiftY*/!* + 'px';
|
ball.style.top = e.pageY - *!*shiftY*/!* + 'px';
|
||||||
}
|
}
|
||||||
|
|
||||||
document.onmousemove = function(e) {
|
document.onmousemove = function(e) {
|
||||||
moveAt(e);
|
moveAt(e);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.onmouseup = function() {
|
ball.onmouseup = function() {
|
||||||
document.onmousemove = self.onmouseup = null;
|
document.onmousemove = null;
|
||||||
|
ball.onmouseup = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -224,12 +188,11 @@ ball.ondragstart = function() {
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
В действии:
|
[online]
|
||||||
|
В действии (внутри ифрейма):
|
||||||
|
|
||||||
<div style="height:80px">
|
[iframe src="ball3" height=230]
|
||||||
Кликните по мячу и тяните, чтобы двигать его.
|
[/online]
|
||||||
<img src="//js.cx/clipart/ball.gif" style="cursor:pointer" width="40" height="40" id="ball3">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
Различие особенно заметно, если захватить мяч за правый-нижний угол. В предыдущем примере мячик "прыгнет" серединой под курсор, в этом -- будет плавно переноситься с текущей позиции.
|
Различие особенно заметно, если захватить мяч за правый-нижний угол. В предыдущем примере мячик "прыгнет" серединой под курсор, в этом -- будет плавно переноситься с текущей позиции.
|
||||||
|
|
||||||
|
@ -240,7 +203,7 @@ ball.ondragstart = function() {
|
||||||
Его компоненты:
|
Его компоненты:
|
||||||
|
|
||||||
<ol>
|
<ol>
|
||||||
<li>События `mousedown` -> `document.mousemove` -> `mouseup`.</li>
|
<li>События `ball.mousedown` -> `document.mousemove` -> `ball.mouseup`.</li>
|
||||||
<li>Передвижение с учётом изначального сдвига `shiftX/shiftY`.</li>
|
<li>Передвижение с учётом изначального сдвига `shiftX/shiftY`.</li>
|
||||||
<li>Отмена действия браузера по событию `dragstart`.</li>
|
<li>Отмена действия браузера по событию `dragstart`.</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
@ -250,10 +213,7 @@ ball.ondragstart = function() {
|
||||||
<ul>
|
<ul>
|
||||||
<li>При `mouseup` можно обработать окончание переноса, произвести изменения в данных, если они нужны.</li>
|
<li>При `mouseup` можно обработать окончание переноса, произвести изменения в данных, если они нужны.</li>
|
||||||
<li>Во время самого переноса можно подсвечивать элементы, над которыми проходит элемент.</li>
|
<li>Во время самого переноса можно подсвечивать элементы, над которыми проходит элемент.</li>
|
||||||
|
<li>При обработке событий `mousedown` и `mouseup` можно использовать делегирование, так что одного обработчика достаточно для управления переносом в зоне с сотнями элементов.
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
Это и многое другое мы рассмотрим в статье про [Drag'n'Drop объектов](/drag-and-drop-objects).
|
Это и многое другое мы рассмотрим в статье про [Drag'n'Drop объектов](/drag-and-drop-objects).
|
||||||
|
|
||||||
[libs]
|
|
||||||
getCoords.js
|
|
||||||
[/libs]
|
|
44
2-ui/3-event-details/4-drag-and-drop/ball.view/index.html
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
</head>
|
||||||
|
<body style="height: 200px">
|
||||||
|
|
||||||
|
<p>Кликните по мячу и тяните, чтобы двигать его.</p>
|
||||||
|
|
||||||
|
<img src="//js.cx/clipart/ball.svg" style="cursor:pointer" width="40" height="40" id="ball">
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var ball = document.getElementById('ball');
|
||||||
|
|
||||||
|
ball.onmousedown = function(e) {
|
||||||
|
|
||||||
|
ball.style.position = 'absolute';
|
||||||
|
moveAt(e);
|
||||||
|
|
||||||
|
document.body.appendChild(ball);
|
||||||
|
|
||||||
|
ball.style.zIndex = 1000; // показывать мяч над другими элементами
|
||||||
|
|
||||||
|
function moveAt(e) {
|
||||||
|
ball.style.left = e.pageX - ball.offsetWidth/2 + 'px';
|
||||||
|
ball.style.top = e.pageY - ball.offsetHeight/2 + 'px';
|
||||||
|
}
|
||||||
|
|
||||||
|
document.onmousemove = function(e) {
|
||||||
|
moveAt(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
ball.onmouseup = function() {
|
||||||
|
document.onmousemove = null;
|
||||||
|
ball.onmouseup = null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
49
2-ui/3-event-details/4-drag-and-drop/ball2.view/index.html
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
</head>
|
||||||
|
<body style="height: 200px">
|
||||||
|
|
||||||
|
<p>Кликните по мячу и тяните, чтобы двигать его.</p>
|
||||||
|
|
||||||
|
<img src="//js.cx/clipart/ball.svg" style="cursor:pointer" width="40" height="40" id="ball">
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var ball = document.getElementById('ball');
|
||||||
|
|
||||||
|
ball.onmousedown = function(e) {
|
||||||
|
|
||||||
|
ball.style.position = 'absolute';
|
||||||
|
moveAt(e);
|
||||||
|
|
||||||
|
document.body.appendChild(ball);
|
||||||
|
|
||||||
|
ball.style.zIndex = 1000; // показывать мяч над другими элементами
|
||||||
|
|
||||||
|
function moveAt(e) {
|
||||||
|
ball.style.left = e.pageX - ball.offsetWidth/2 + 'px';
|
||||||
|
ball.style.top = e.pageY - ball.offsetHeight/2 + 'px';
|
||||||
|
}
|
||||||
|
|
||||||
|
document.onmousemove = function(e) {
|
||||||
|
moveAt(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
ball.onmouseup = function() {
|
||||||
|
document.onmousemove = null;
|
||||||
|
ball.onmouseup = null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ball.ondragstart = function() {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
63
2-ui/3-event-details/4-drag-and-drop/ball3.view/index.html
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
</head>
|
||||||
|
<body style="height: 200px">
|
||||||
|
|
||||||
|
<p>Кликните по мячу и тяните, чтобы двигать его.</p>
|
||||||
|
|
||||||
|
<img src="//js.cx/clipart/ball.svg" style="cursor:pointer" width="40" height="40" id="ball">
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var ball = document.getElementById('ball');
|
||||||
|
|
||||||
|
ball.onmousedown = function(e) {
|
||||||
|
|
||||||
|
var coords = getCoords(ball);
|
||||||
|
var shiftX = e.pageX - coords.left;
|
||||||
|
var shiftY = e.pageY - coords.top;
|
||||||
|
|
||||||
|
ball.style.position = 'absolute';
|
||||||
|
document.body.appendChild(ball);
|
||||||
|
moveAt(e);
|
||||||
|
|
||||||
|
ball.style.zIndex = 1000; // над другими элементами
|
||||||
|
|
||||||
|
function moveAt(e) {
|
||||||
|
ball.style.left = e.pageX - shiftX + 'px';
|
||||||
|
ball.style.top = e.pageY - shiftY + 'px';
|
||||||
|
}
|
||||||
|
|
||||||
|
document.onmousemove = function(e) {
|
||||||
|
moveAt(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
ball.onmouseup = function() {
|
||||||
|
document.onmousemove = null;
|
||||||
|
ball.onmouseup = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ball.ondragstart = function() {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function getCoords(elem) { // кроме IE8-
|
||||||
|
var box = elem.getBoundingClientRect();
|
||||||
|
|
||||||
|
return {
|
||||||
|
top: box.top + pageYOffset,
|
||||||
|
left: box.left + pageXOffset
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
0
2-ui/3-event-details/6-drag-and-drop/ball_shift.png → 2-ui/3-event-details/4-drag-and-drop/ball_shift.png
Executable file → Normal file
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 8.7 KiB |
0
2-ui/3-event-details/6-drag-and-drop/ball_shift@2x.png → 2-ui/3-event-details/4-drag-and-drop/ball_shift@2x.png
Executable file → Normal file
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
@ -1,34 +1,40 @@
|
||||||
# Мышь: Drag'n'Drop объектов
|
# Мышь: Drag'n'Drop более глубоко
|
||||||
|
|
||||||
В [предыдущей статье](/drag-and-drop) мы рассмотрели основы Drag'n'Drop. Здесь мы построим на этой основе фреймворк, предназначенный для переноса *объектов* -- элементов списка, узлов дерева и т.п.
|
В [предыдущей статье](/drag-and-drop) мы рассмотрели основы Drag'n'Drop. Здесь мы разберём дополнительные "тонкие места" и приёмы реализации, которые возникают на практике.
|
||||||
|
|
||||||
|
Почти все javascript-библиотеки реализуют Drag'n'Drop так, как написано (хотя бывает что и менее эффективно).
|
||||||
|
|
||||||
|
Зная, что и как, вы сможете легко написать свой код переноса или поправить, адаптировать существующую библиотеку под себя.
|
||||||
|
|
||||||
|
Этот материал не строго обязателен для изучения, он специфичен именно для Drag'n'Drop.
|
||||||
|
|
||||||
Почти все javascript-библиотеки реализуют Drag'n'Drop так, как написано (хотя бывает что и менее эффективно ;)). Зная, что и как, вы сможете легко написать свой код переноса или поправить, адаптировать существующую библиотеку под себя.
|
|
||||||
[cut]
|
[cut]
|
||||||
|
|
||||||
## Документ
|
## Документ
|
||||||
|
|
||||||
Как пример задачи -- возьмём документ с иконками браузера ("объекты переноса"), которые можно переносить в компьютер ("цель переноса"):
|
Как пример задачи -- возьмём документ с иконками браузера ("объекты переноса"), которые можно переносить в компьютер ("цель переноса"):
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>Элементы, которые можно переносить (иконки браузеров), помечены атрибутом `draggable`.</li>
|
<li>Элементы, которые можно переносить (иконки браузеров), помечены классом `draggable`.</li>
|
||||||
<li>Элементы, на которые можно положить (компьютер), имеют атрибут `droppable`.</li>
|
<li>Элементы, на которые можно положить (компьютер), имеют класс `droppable`.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<img src="chrome.png" *!*draggable*/!*>
|
<img src="chrome.png" class="*!*draggable*/!*">
|
||||||
<img src="firefox.png" *!*draggable*/!*>
|
<img src="firefox.png" class="*!*draggable*/!*">
|
||||||
<img src="ie.png" *!*draggable*/!*>
|
<img src="ie.png" class="*!*draggable*/!*">
|
||||||
<img src="opera.png" *!*draggable*/!*>
|
<img src="opera.png" class="*!*draggable*/!*">
|
||||||
<img src="safari.png" *!*draggable*/!*>
|
<img src="safari.png" class="*!*draggable*/!*">
|
||||||
|
|
||||||
<p>Браузер переносить сюда:</p>
|
<p>Браузер переносить сюда:</p>
|
||||||
|
|
||||||
<img src="computer.gif" *!*droppable*/!*>
|
<img src="computer.gif" class="*!*droppable*/!*">
|
||||||
```
|
```
|
||||||
|
|
||||||
Работающий пример с переносом.
|
|
||||||
|
|
||||||
|
Работающий пример с переносом:
|
||||||
|
|
||||||
[iframe border=1 src="dragDemo" height=250 link edit]
|
[iframe border=1 src="dragDemo" height=280 link edit]
|
||||||
|
|
||||||
Далее мы рассмотрим, как делается фреймворк для таких переносов, а в перспективе -- и для более сложных.
|
Далее мы рассмотрим, как делается фреймворк для таких переносов, а в перспективе -- и для более сложных.
|
||||||
|
|
||||||
|
@ -41,17 +47,15 @@
|
||||||
|
|
||||||
## Начало переноса
|
## Начало переноса
|
||||||
|
|
||||||
Чтобы начать перенос элемента, мы должны отловить нажатие левой кнопки мыши на нём. Для этого используем событие `mousedown`.
|
Чтобы начать перенос элемента, мы должны отловить нажатие левой кнопки мыши на нём. Для этого используем событие `mousedown`... И, конечно, делегирование.
|
||||||
|
|
||||||
..И здесь нас ждёт первая особенность переноса объектов. На что ставить обработчик?
|
Переносимых элементов может быть много. В нашем документе-примере это всего лишь несколько иконок, но если мы хотим переносить элементы списка или дерева, то их может быть 100 штук и более.
|
||||||
|
|
||||||
**Переносимых элементов может быть много.** В нашем документе-примере это всего лишь несколько иконок, но если мы хотим переносить элементы списка или дерева, то их может быть 100 штук и более.
|
Поэтому повесим обработчик `mousedown` на контейнер, который содержит переносимые элементы, и будем определять нужный элемент поиском ближайшего `draggable` вверх по иерархии от `event.target`.
|
||||||
|
|
||||||
Назначать обработчик на каждый DOM-элемент -- нецелесообразно. Проще всего решить эту задачу делегированием.
|
В качестве контейнера здесь будем брать `document`, хотя это может быть и любой элемент.
|
||||||
|
|
||||||
**Повесим обработчик `mousedown` на контейнер, который содержит переносимые элементы,** и будем определять нужный элемент поиском ближайшего `draggable` вверх по иерархии от `event.target`. В качестве контейнера здесь и далее будем брать `document`.
|
Найденный `draggable`-элемент сохраним в свойстве `dragObject.elem` и начнём двигать.
|
||||||
|
|
||||||
Найденный переносимый объект сохраним в переменной `dragObject` и начнём двигать.
|
|
||||||
|
|
||||||
Код обработчика `mousedown`:
|
Код обработчика `mousedown`:
|
||||||
|
|
||||||
|
@ -60,14 +64,13 @@ var dragObject = {};
|
||||||
|
|
||||||
document.onmousedown = function(e) {
|
document.onmousedown = function(e) {
|
||||||
|
|
||||||
if (e.which != 1) {
|
if (e.which != 1) { // если клик правой кнопкой мыши
|
||||||
return; // нажатие правой кнопкой не запускает перенос
|
return; // то он не запускает перенос
|
||||||
}
|
}
|
||||||
|
|
||||||
// найти ближайший draggable, пройдя по цепочке родителей target
|
var elem = e.target.closest('.draggable');
|
||||||
var elem = findDraggable(e.target, document);
|
|
||||||
|
|
||||||
if (!elem) return; // не нашли, клик вне draggable объекта
|
if (!elem) return; // не нашли, клик вне draggable-объекта
|
||||||
|
|
||||||
// запомнить переносимый объект
|
// запомнить переносимый объект
|
||||||
dragObject.elem = elem;
|
dragObject.elem = elem;
|
||||||
|
@ -78,33 +81,23 @@ document.onmousedown = function(e){
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
В обработчике используется функция `findDraggable` для поиска переносимого элемента по `event.target`:
|
[warn header="Не начинаем перенос по `mousedown`"]
|
||||||
|
Ранее мы по `mousedown` начинали перенос.
|
||||||
|
|
||||||
```js
|
Но на самом деле нажатие на элемент вовсе не означает, что его собираются куда-то двигать. Возможно, на нём просто кликают.
|
||||||
function findDraggable(event) {
|
|
||||||
var elem = event.target;
|
|
||||||
|
|
||||||
// найти ближайший draggable, пройдя по цепочке родителей target
|
Это важное различие. Снимать элемент со своего места и куда-то двигать нужно только при переносе.
|
||||||
while(elem != document && elem.getAttribute('draggable') == null) {
|
|
||||||
elem = elem.parentNode;
|
|
||||||
}
|
|
||||||
return elem == document ? null : elem;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Здесь мы пока не начинаем перенос, потому что нажатие на элемент вовсе не означает, что его собираются куда-то двигать.** Возможно, на нём просто кликают.
|
Чтобы отличить перенос от клика, в том числе -- от клика, который сопровождается нечаянным перемещением на пару пикселей (рука дрогнула), мы будем запоминать в `dragObject`, какой элемент (`elem`) и где (`downX/downY`) был зажат, а начало переноса будем инициировать из `mousemove`, если он передвинут хотя бы на несколько пикселей.
|
||||||
|
[/warn]
|
||||||
Это важное различие. Нужно отличать перенос от клика, в том числе -- от клика, который сопровождается нечаянным перемещением на пару пикселей (рука дрогнула).
|
|
||||||
|
|
||||||
Иначе при клике элемент будет сниматься со своего места, и потом тут же возвращаться обратно (никуда не положили). Это лишняя работа и, вообще, выглядит некрасиво.
|
|
||||||
|
|
||||||
**Поэтому в `mousedown` мы запоминаем, какой элемент и где был зажат, а начало переноса будем инициировать из `mousemove`,** если он передвинут хотя бы на несколько пикселей.
|
|
||||||
|
|
||||||
## Перенос элемента
|
## Перенос элемента
|
||||||
|
|
||||||
Первой задачей обработчика `mousemove` является инициировать начало переноса, если элемент передвинули в зажатом состоянии.
|
Первой задачей обработчика `mousemove` является инициировать начало переноса, если элемент передвинули в зажатом состоянии.
|
||||||
|
|
||||||
Ну а второй задачей -- отображать его перенос при каждом передвижении мыши. Схематично, обработчик имеет такой вид:
|
Ну а второй задачей -- отображать его перенос при каждом передвижении мыши.
|
||||||
|
|
||||||
|
Схематично, обработчик будет иметь такой вид:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
document.onmousemove = function(e) {
|
document.onmousemove = function(e) {
|
||||||
|
@ -118,13 +111,13 @@ document.onmousemove = function(e) {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Здесь мы видим новое свойство `dragObject.avatar`. При начале переноса *аватар* делается из элемента и сохраняется в свойство `dragObject.avatar`.
|
Здесь мы видим новое свойство `dragObject.avatar`. При начале переноса "аватар" делается из элемента и сохраняется в свойство `dragObject.avatar`.
|
||||||
|
|
||||||
***"Аватар"* -- это DOM-элемент, который перемещается по экрану.**
|
**"Аватар" -- это DOM-элемент, который перемещается по экрану.**
|
||||||
|
|
||||||
Почему бы не перемещать по экрану сам `draggable`-элемент? Зачем, вообще, нужен аватар?
|
Почему бы не перемещать по экрану сам `draggable`-элемент? Зачем, вообще, нужен аватар?
|
||||||
|
|
||||||
Дело в том, что иногда сам элемент передвигать неудобно, например он слишком большой. А удобно создать некоторое визуальное представление элемента, и его уже переносить. Аватар дает такую возможность.
|
Дело в том, что иногда сам элемент передвигать неудобно, например потому, что он слишком большой. А удобно создать некоторое визуальное представление элемента, и его уже переносить. Аватар дает такую возможность.
|
||||||
|
|
||||||
А в простейшем случае аватаром можно будет сделать сам элемент, и это не повлечёт дополнительных расходов.
|
А в простейшем случае аватаром можно будет сделать сам элемент, и это не повлечёт дополнительных расходов.
|
||||||
|
|
||||||
|
@ -154,7 +147,7 @@ avatar.style.left = новая координата + 'px';
|
||||||
avatar.style.top = новая координата + 'px';
|
avatar.style.top = новая координата + 'px';
|
||||||
```
|
```
|
||||||
|
|
||||||
**Как вычислять новые координаты `left/top` при переносе?**
|
Как вычислять новые координаты `left/top` при переносе?
|
||||||
|
|
||||||
Чтобы элемент сохранял свою позицию под курсором, необходимо при нажатии запомнить его изначальный сдвиг относительно курсора, и сохранять его при переносе.
|
Чтобы элемент сохранял свою позицию под курсором, необходимо при нажатии запомнить его изначальный сдвиг относительно курсора, и сохранять его при переносе.
|
||||||
|
|
||||||
|
@ -169,7 +162,7 @@ avatar.style.left = e.pageX - shiftX + 'px';
|
||||||
avatar.style.top = e.pageY - shiftY + 'px';
|
avatar.style.top = e.pageY - shiftY + 'px';
|
||||||
```
|
```
|
||||||
|
|
||||||
### Полный код mousemove
|
## Полный код mousemove
|
||||||
|
|
||||||
Код `mousemove`, решающий задачу начала переноса и позиционирования аватара:
|
Код `mousemove`, решающий задачу начала переноса и позиционирования аватара:
|
||||||
|
|
||||||
|
@ -218,7 +211,6 @@ document.onmousemove = function(e) {
|
||||||
В нашем случае для отмены переноса нужно запомнить старую позицию элемента и его родителя.
|
В нашем случае для отмены переноса нужно запомнить старую позицию элемента и его родителя.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ hide="Раскрыть код createAvatar"
|
|
||||||
function createAvatar(e) {
|
function createAvatar(e) {
|
||||||
|
|
||||||
// запомнить старые свойства, чтобы вернуться к ним при отмене переноса
|
// запомнить старые свойства, чтобы вернуться к ним при отмене переноса
|
||||||
|
@ -245,7 +237,7 @@ function createAvatar(e) {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Функция `startDrag(e)` инициирует начало переноса и позиционирует аватар на странице.
|
Функция `startDrag(e)`, которую вызывает `mousemove`, если видит, что элемент в "зажатом" состоянии перенесли достаточно далеко, инициирует начало переноса и позиционирует аватар на странице:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
function startDrag(e) {
|
function startDrag(e) {
|
||||||
|
@ -257,7 +249,7 @@ function startDrag(e) {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Окончание переноса
|
## Окончание переноса
|
||||||
|
|
||||||
Окончание переноса происходит по событию `mouseup`.
|
Окончание переноса происходит по событию `mouseup`.
|
||||||
|
|
||||||
|
@ -306,18 +298,16 @@ function finishDrag(e) {
|
||||||
|
|
||||||
```js
|
```js
|
||||||
// возвратит ближайший droppable или null
|
// возвратит ближайший droppable или null
|
||||||
// *!*предварительный вариант findDroppable, исправлен ниже!*/!*
|
*!*
|
||||||
|
// это предварительный вариант findDroppable, исправлен ниже!
|
||||||
|
*/!*
|
||||||
function findDroppable(event) {
|
function findDroppable(event) {
|
||||||
|
|
||||||
// взять элемент на данных координатах
|
// взять элемент на данных координатах
|
||||||
var elem = document.elementFromPoint(event.clientX, event.clientY);
|
var elem = document.elementFromPoint(event.clientX, event.clientY);
|
||||||
|
|
||||||
// пройти вверх по цепочке родителей в поисках droppable
|
// найти ближайший сверху droppable
|
||||||
while(elem != document && elem.getAttribute('droppable') == null) {
|
return elem.closest('.droppable');
|
||||||
elem = elem.parentNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
return elem == document ? null : elem;
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -325,67 +315,48 @@ function findDroppable(event) {
|
||||||
|
|
||||||
Вариант выше -- предварительный. Он не будет работать. Если попробовать применить эту функцию, будет все время возвращать один и тот же элемент! А именно -- *текущий переносимый*. Почему так?
|
Вариант выше -- предварительный. Он не будет работать. Если попробовать применить эту функцию, будет все время возвращать один и тот же элемент! А именно -- *текущий переносимый*. Почему так?
|
||||||
|
|
||||||
..Дело в том, что в процессе переноса под мышкой находится именно аватар. При начале переноса ему даже `z-index` ставится большой, чтобы он был поверх всех остальных.
|
...Дело в том, что в процессе переноса под мышкой находится именно аватар. При начале переноса ему даже `z-index` ставится большой, чтобы он был поверх всех остальных.
|
||||||
|
|
||||||
**Аватар перекрывает остальные элементы. Поэтому функция `document.elementFromPoint()` увидит на текущих координатах именно его.**
|
**Аватар перекрывает остальные элементы. Поэтому функция `document.elementFromPoint()` увидит на текущих координатах именно его.**
|
||||||
|
|
||||||
Чтобы это изменить, нужно либо поправить код переноса, чтобы аватар двигался *рядом* с курсором мыши, либо поступить проще:
|
Чтобы это изменить, нужно либо поправить код переноса, чтобы аватар двигался *рядом* с курсором мыши, либо дать аватару стиль `pointer-events:none` (кроме IE10-), либо:
|
||||||
|
|
||||||
<ol><li>Спрятать аватар.</li>
|
<ol><li>Спрятать аватар.</li>
|
||||||
<li>Вызывать `elementFromPoint`.</li>
|
<li>Вызывать `elementFromPoint`.</li>
|
||||||
<li>Показать аватар.</li></ol>
|
<li>Показать аватар.</li></ol>
|
||||||
|
|
||||||
Напишем вспомогательную функцию `getElementUnderClientXY(elem, clientX, clientY)`, которая это делает:
|
Напишем функцию `findDroppable(event)`, которая это делает:
|
||||||
|
|
||||||
```js
|
|
||||||
/* получить элемент на координатах clientX/clientY, под elem */
|
|
||||||
function getElementUnderClientXY(elem, clientX, clientY) {
|
|
||||||
// сохранить старый display и спрятать переносимый элемент
|
|
||||||
var display = elem.style.display || '';
|
|
||||||
elem.style.display = 'none';
|
|
||||||
|
|
||||||
// получить самый вложенный элемент под курсором мыши
|
|
||||||
var target = document.elementFromPoint(clientX, clientY);
|
|
||||||
|
|
||||||
// показать переносимый элемент обратно
|
|
||||||
elem.style.display = display;
|
|
||||||
|
|
||||||
if (!target || target == document) { // такое бывает при выносе за границы окна
|
|
||||||
target = document.body; // поправить значение, чтобы был именно элемент
|
|
||||||
}
|
|
||||||
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Правильный код `findDroppable`:
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
function findDroppable(event) {
|
function findDroppable(event) {
|
||||||
var elem = getElementUnderClientXY(dragObject.avatar, event.clientX, event.clientY);
|
// спрячем переносимый элемент
|
||||||
|
dragObject.avatar.hidden = true;
|
||||||
|
|
||||||
while(elem != document && elem.getAttribute('droppable') == null) {
|
// получить самый вложенный элемент под курсором мыши
|
||||||
elem = elem.parentNode;
|
var elem = document.elementFromPoint(event.clientX, event.clientY);
|
||||||
|
|
||||||
|
// показать переносимый элемент обратно
|
||||||
|
dragObject.avatar.hidden = false;
|
||||||
|
|
||||||
|
if (elem == null) {
|
||||||
|
// такое возможно, если курсор мыши "вылетел" за границу окна
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return elem == document ? null : elem;
|
return target.closest('.droppable');
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Сводим части фреймворка вместе
|
## DragManager
|
||||||
|
|
||||||
Сейчас в нашем распоряжении находятся основные фрагменты кода и решения всех технических подзадач.
|
Из фрагментов кода, разобранных выше, можно собрать мини-фреймворк.
|
||||||
|
|
||||||
Приведем их в нормальный вид.
|
Объект `DragManager` будет запоминать текущий переносимый объект и отслеживать его перенос.
|
||||||
|
|
||||||
### dragManager
|
|
||||||
|
|
||||||
Перенос будет координироваться единым объектом. Назовем его `dragManager`.
|
|
||||||
|
|
||||||
Для его создания используем не обычный синтаксис `{...}`, а вызов `new function`. Это позволит прямо при создании объявить дополнительные переменные и функции в замыкании, которыми могут пользоваться методы объекта, а также назначить обработчики:
|
Для его создания используем не обычный синтаксис `{...}`, а вызов `new function`. Это позволит прямо при создании объявить дополнительные переменные и функции в замыкании, которыми могут пользоваться методы объекта, а также назначить обработчики:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
var dragManager = new function() {
|
var DragManager = new function() {
|
||||||
|
|
||||||
var dragObject = {};
|
var dragObject = {};
|
||||||
|
|
||||||
|
@ -404,11 +375,13 @@ var dragManager = new function() {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Всю работу будут выполнять обработчики `onMouse*`, которые оформлены как локальные функции. В данном случае они ставятся на `document` через `on...`, но вы легко можете поменять это на `addEventListener/attachEvent`.
|
Всю работу будут выполнять обработчики `onMouse*`, которые оформлены как локальные функции. В данном случае они ставятся на `document` через `on...`, но это легко поменять это на `addEventListener`.
|
||||||
|
|
||||||
|
Код функция `onMouse*` мы подробно рассмотрели ранее, но вы сможете увидеть их в полном примере ниже.
|
||||||
|
|
||||||
Внутренний объект `dragObject` будет содержать информацию об объекте переноса.
|
Внутренний объект `dragObject` будет содержать информацию об объекте переноса.
|
||||||
|
|
||||||
У него будут следующие свойства:
|
У него будут следующие свойства, которые также разобраны выше:
|
||||||
<dl>
|
<dl>
|
||||||
<dt>`elem`</dt>
|
<dt>`elem`</dt>
|
||||||
<dd>Текущий зажатый мышью объект, если есть (ставится в `mousedown`).</dd>
|
<dd>Текущий зажатый мышью объект, если есть (ставится в `mousedown`).</dd>
|
||||||
|
@ -420,63 +393,56 @@ var dragManager = new function() {
|
||||||
<dd>Относительный сдвиг курсора от угла элемента, вспомогательное свойство вычисляется в начале переноса.</dd>
|
<dd>Относительный сдвиг курсора от угла элемента, вспомогательное свойство вычисляется в начале переноса.</dd>
|
||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
**Задачей `dragManager` является общее управление переносом.** Для обработки его окончания вызываются методы `onDrag*`, которые назначаются внешним кодом.
|
Задачей `DragManager` является общее управление переносом. Что же касается действий при его окончании -- их должен назначить внешний код, который использует `DragManager`.
|
||||||
|
|
||||||
Разработчик, подключив `dragManager`, описывает в этих методах, что делать при начале и завершении переноса.
|
Можно сделать это через вспомогательные методы `onDrag*`, которые устанавливаются внешним кодом и затем вызываются фреймворком. Разработчик, подключив `DragManager`, описывает в этих методах, что делать при начале и завершении переноса. Конечно же, можно заменить методы `onDrag*` на генерацию "своих" событий.
|
||||||
|
|
||||||
[smart]
|
С использованием `DragManager` пример, с которого начиналась эта глава -- перенос иконок браузеров в компьютер, реализуется совсем просто:
|
||||||
Если в вашем распоряжении есть современный JavaScript-фреймворк, то можно заменить вызовы методов `onDrag*` на генерацию соответствующих событий.
|
|
||||||
[/smart]
|
|
||||||
|
|
||||||
### Реализация переноса иконок
|
|
||||||
|
|
||||||
С использованием этого фреймворка перенос иконок браузеров в компьютер реализуется просто:
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
dragManager.onDragEnd = function(dragObject, dropElem) {
|
DragManager.onDragEnd = function(dragObject, dropElem) {
|
||||||
|
|
||||||
|
// скрыть/удалить переносимый объект
|
||||||
|
dragObject.elem.hidden = true;
|
||||||
|
|
||||||
// успешный перенос, показать улыбку классом computer-smile
|
// успешный перенос, показать улыбку классом computer-smile
|
||||||
dropElem.className = 'computer computer-smile';
|
dropElem.className = 'computer computer-smile';
|
||||||
|
|
||||||
// скрыть переносимый объект
|
|
||||||
dragObject.elem.style.display = 'none';
|
|
||||||
|
|
||||||
// убрать улыбку через 0.2 сек
|
// убрать улыбку через 0.2 сек
|
||||||
setTimeout(function() { dropElem.className = 'computer'; }, 200);
|
setTimeout(function() {
|
||||||
|
dropElem.classList.remove('computer-smile';
|
||||||
|
}, 200);
|
||||||
};
|
};
|
||||||
|
|
||||||
dragManager.onDragCancel = function(dragObject) {
|
DragManager.onDragCancel = function(dragObject) {
|
||||||
// откат переноса
|
// откат переноса
|
||||||
dragObject.avatar.rollback();
|
dragObject.avatar.rollback();
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
Результат:
|
Полный пример с кодом:
|
||||||
|
|
||||||
|
[codetabs src="dragDemo" height=280]
|
||||||
[iframe border=1 src="dragDemo" height=250 link edit]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Варианты расширения этого кода
|
## Расширения
|
||||||
|
|
||||||
Существует масса возможных применений Drag'n'Drop. Реализациях всех из них здесь превратила бы довольно простой фреймворк в страшнейшего монстра.
|
Существует масса возможных применений Drag'n'Drop. Здесь мы не будем реализовывать их все, поскольку не стоит цель создать фреймворк-монстр.
|
||||||
|
|
||||||
Поэтому мы разберем варианты расширений и их реализации, чтобы вы, при необходимости, легко могли написать то, что нужно.
|
Однако, мы рассмотрим их, чтобы, при необходимости, легко было написать то, что нужно.
|
||||||
|
|
||||||
### Захватывать элемент можно только за "ручку"
|
### Захватывать элемент можно только за "ручку"
|
||||||
|
|
||||||
Функция `createAvatar(e)` может быть модифицирована, чтобы захватывать элемент можно было, только ухватившись за определенную зону.
|
Часто бывает, что перенос должен быть инициирован только при захвате за определённую зону элемента. К примеру, модальное окно можно "взять", только захватив его за заголовок.
|
||||||
|
|
||||||
Например, окно чата можно "взять", только захватив его за заголовок.
|
Для этого достаточно добавить необходимую проверку, к примеру, в функцию `createAvatar` или перед её запуском.
|
||||||
|
|
||||||
Для этого достаточно проверять по `e.target`, куда, всё же, нажал посетитель. Если взять элемент на данных координатах нельзя, то `createAvatar` может вернуть `false`, и перенос не будет начат.
|
Если `mousedown` был внутри элемента, помеченного, к примеру, классом `draghandle`, то начинаем перенос, иначе -- нет.
|
||||||
|
|
||||||
### Проверка прав положить на droppable
|
### Проверка прав на droppable
|
||||||
|
|
||||||
Бывает так, что не на любое место в `droppable` можно положить элемент.
|
Бывает и так, что не на любое место в `droppable` можно положить элемент.
|
||||||
|
|
||||||
Само решение "можно или нет" может зависеть как от переносимого элемента (или "аватара" как его полномочного представителя), так и от конкретного места в `droppable`, над которым посетитель отпустил кнопку мыши.
|
|
||||||
|
|
||||||
Например: в админке есть дерево всех объектов сайта: статей, разделов, посетителей и т.п.
|
Например: в админке есть дерево всех объектов сайта: статей, разделов, посетителей и т.п.
|
||||||
|
|
||||||
|
@ -486,31 +452,19 @@ dragManager.onDragCancel = function(dragObject) {
|
||||||
<li>Узел "статья" (draggable) можно переносить в "раздел" (droppable), а узел "пользователи" -- нельзя. Но и то и другое можно поместить в "корзину".</li>
|
<li>Узел "статья" (draggable) можно переносить в "раздел" (droppable), а узел "пользователи" -- нельзя. Но и то и другое можно поместить в "корзину".</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
Эта задача решается добавлением проверки в `findDroppable(e)`. Эта функция знает и об аватаре и о событии. При попытке положить в "неправильное" место функция `findDroppable(e)` должна возвращать `null`.
|
Здесь решение: "можно или нет" переносить или нельзя зависит от "типа" переносимого объекта.
|
||||||
|
|
||||||
Рассмотрим эту задачу поглубже, так как она встречается часто.
|
Есть и более сложные варианты, когда решение зависит от конкретного места в `droppable`, над которым посетитель отпустил кнопку мыши. К примеру, переносить в верхнюю часть можно, а в нижнюю -- нет.
|
||||||
|
|
||||||
**Есть две наиболее частые причины, по которым аватар нельзя положить на потенциальный `droppable`.**
|
Эта задача решается добавлением проверки в `findDroppable(e)`. Эта функция знает и об аватаре и о событии, включая координаты. При попытке положить в "неправильное" место функция `findDroppable(e)` должна возвращать `null`.
|
||||||
|
|
||||||
Первую мы уже рассмотрели: "несовпадение типов". Это как раз тот случай, когда "узлы-разделы" дерева админки могут принимать только "статьи", и не могут -- "посетителей".
|
Однако, на практике бывают ситуации, когда решение "прямо сейчас" принять невозможно. Например, нужно сделать запрос на сервер: "А разрешено ли текущему посетителю производить такую операцию?"
|
||||||
|
|
||||||
В этом случае мы можем проверить это в `findDroppable`, так как вся необходимая информация о типах у нас есть.
|
Как при этом должен вести себя интерфейс? Можно, конечно сделать, чтобы элемент после отпускания кнопки мыши "завис" над `droppable`, ожидая ответа. Однако, такое решение неудобно в реализации и странновато выглядит для посетителя.
|
||||||
|
|
||||||
Вторая причина -- посложнее.
|
Как правило, применяют "оптимистичный" алгоритм, по которому мы считаем, что перенос обычно успешен, но при необходимости можем отменить его.
|
||||||
|
|
||||||
**Может не хватать прав на такое действие с `draggable/droppable`, в рамках наложенных на посетителя ограничений.**
|
При нём посетитель кладет объект туда, куда он хочет, а затем, в коде `onDragEnd`:
|
||||||
|
|
||||||
Например, человек не может положить статью именно в данный раздел.
|
|
||||||
|
|
||||||
А возможно, объект переноса удален из базы администратором, и браузер об этом (пока) не знает -- мы должны корректно обрабатывать все случаи, включая этот.
|
|
||||||
|
|
||||||
Здесь сложность в том, что **окончательное решение знает только сервер**. Значит, нужно сделать запрос. А элемент после отпускания мыши не может "зависнуть" над элементом в ожидании ответа, нужно его куда-то положить.
|
|
||||||
|
|
||||||
**Как правило, применяют "оптимистичный" сценарий, по которому мы считаем, что перенос обычно успешен.**
|
|
||||||
|
|
||||||
При нём посетитель кладет объект туда, куда он хочет.
|
|
||||||
|
|
||||||
Затем, в коде `onDragEnd`:
|
|
||||||
<ol>
|
<ol>
|
||||||
<li>Визуально обрабатывается завершение переноса, как будто все ок.</li>
|
<li>Визуально обрабатывается завершение переноса, как будто все ок.</li>
|
||||||
<li>Производится асинхронный запрос к серверу, содержащий информацию о переносе.</li>
|
<li>Производится асинхронный запрос к серверу, содержащий информацию о переносе.</li>
|
||||||
|
@ -518,93 +472,58 @@ dragManager.onDragCancel = function(dragObject) {
|
||||||
<li>Если нет -- выводится ошибка и возвращается `avatar.rollback()`. Аватар в этом случае должен предусматривать возможность отката после успешного завершения.</li>
|
<li>Если нет -- выводится ошибка и возвращается `avatar.rollback()`. Аватар в этом случае должен предусматривать возможность отката после успешного завершения.</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
**Процесс общения с сервером сопровождается индикацией загрузки и, при необходимости, блокировкой новых операций переноса до получения подтверждения.**
|
Процесс общения с сервером сопровождается индикацией загрузки и, при необходимости, блокировкой новых операций переноса до получения подтверждения.
|
||||||
</dd>
|
|
||||||
</dl>
|
|
||||||
|
|
||||||
|
|
||||||
### Индикация переноса
|
### Подсветка текущего droppable
|
||||||
|
|
||||||
Можно добавить методы `onDragEnter/onDragMove/onDragLeave` для интеграции с внешним кодом, которые вызываются при заходе (`onDragEnter`), при каждом передвижении (`onDragMove`), и при выходе из `droppable` (`onDragLeave`).
|
Удобно, когда пользователь во время переноса наглядно видит, куда он сейчас положит draggable. Например, текущий droppable (или его часть) подсвечиваются.
|
||||||
|
|
||||||
При этом бывает, что нужно поддерживать перенос *в элемент*, но и перенос *между элементами*. Разберем два варианта такой ситуации:
|
Для этого в `DragManager` можно добавить дополнительные методы интеграции с внешним кодом:
|
||||||
|
<ul>
|
||||||
|
<li>`onDragEnter` -- будет вызываться при заходе на `droppable`, из `onMouseMove`.</li>
|
||||||
|
<li>`onDragMove` -- при каждом передвижении внутри `droppable`, из `onMouseMove`.</li>
|
||||||
|
<li>`onDragLeave` -- при выходе с `droppable`, из `onMouseMove` и `onMouseUp`.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<dl>
|
Возможен более сложный вариант, когда нужно поддерживать не только перенос *в элемент*, но и перенос *между элементами*, например вставку одной статьи между двумя другими.
|
||||||
<dt>Поддержка трех видов переноса: "между", "под", "над" `droppable`</dt>
|
|
||||||
<dd>Этот сценарий встречается, когда можно вставить статью как *в* существующий "узел-раздел" дерева, так и *между* разделами.
|
|
||||||
|
|
||||||
В этом случае `droppable` делится на 3 части по высоте: 25% - 50% - 25%, берутся координаты элемента и определяется попадание координаты события на нужную часть.
|
Для этого код, который обрабатывает перенос, может "делить на части" droppable, к примеру, в отношении 25% - 50% - 25%, и смотреть:
|
||||||
|
|
||||||
В параметры `dragObject` добавляется флаг, обозначающий, куда по отношению к найденному `droppable` происходит перенос.
|
<ul>
|
||||||
</dd>
|
<li>Если перенос в верхнюю четверть, то это -- "над".</li>
|
||||||
<dt>Поддержка переноса только "между"</dt>
|
<li>Если перенос в середину, то это "внутрь".</li>
|
||||||
<dd>Здесь есть два варианта.
|
<li>Если перенос в нижнюю четверть, то это -- "под".</li>
|
||||||
<ol>
|
</ul>
|
||||||
<li>Во-первых, можно разделить `droppable` в отношении 50%/50% и по координатам смотреть, куда мы попали.</li>
|
|
||||||
<li>В некоторых случаях, например при вставке между `droppable`-элементами списка, можно считать, что *элемент вставляется перед тем `LI`, над которым проходит*.
|
|
||||||
|
|
||||||
А чтобы вставить после последнего -- нужно перетащить аватар на сам DOM-элемент списка, но не на существующий `droppable`, а в пустое место, которое оставляется внизу для этой цели.</li>
|
Текущий `droppable` и позиция относительно него при этом могут помечаться подсветкой и жирной чертой над/под, если требуется.
|
||||||
</ol>
|
|
||||||
</dd>
|
|
||||||
</dl>
|
|
||||||
|
|
||||||
Индикацию "переноса между" удобнее всего делать либо раздвижением элементов, либо показом полосы-индикатора <code>border-top/border-bottom</code>, как показано на рисунке ниже:
|
|
||||||
|
|
||||||
|
Пример индикации из Firefox:
|
||||||
<img src="between.png">
|
<img src="between.png">
|
||||||
|
|
||||||
### Анимация отмены переноса
|
### Анимация отмены переноса
|
||||||
|
|
||||||
Отмену переноса и возврат аватара на место можно красиво анимировать.
|
Отмену переноса и возврат аватара на место можно красиво анимировать.
|
||||||
|
|
||||||
Один из частых вариантов -- скольжение объекта обратно к исходному месту, откуда его взяли. Для этого достаточно поправить `avatar.revert()` соотвествующим образом.
|
Один из частых вариантов -- скольжение объекта обратно к исходному месту, откуда его взяли. Для этого достаточно поправить `avatar.rollback()`.
|
||||||
|
|
||||||
## Итого
|
## Итого
|
||||||
|
|
||||||
Алгоритм Drag'n'Drop:
|
Уточнённый алгоритм Drag'n'Drop:
|
||||||
<ol>
|
<ol>
|
||||||
<li>При `mousedown` запомнить координаты нажатия.</li>
|
<li>При `mousedown` запомнить координаты нажатия.</li>
|
||||||
<li>При `mousemove` инициировать перенос, как только зажатый элемент передвинули на 3 пикселя или больше.
|
<li>При `mousemove` инициировать перенос, как только зажатый элемент передвинули на 3 пикселя или больше. Сообщить во внешний код вызовом `onDragStart`.</li>
|
||||||
<ol>
|
<ol>
|
||||||
<li>Создать аватар, если можно начать перенос с этой точки `draggable`.</li>
|
<li>Создать аватар, если можно начать перенос с этой точки `draggable`.</li>
|
||||||
<li>Перемещать его по экрану. Новые координаты ставить по `e.pageX/pageY` с учетом изначального сдвига элемента относительно курсора.</li>
|
<li>Перемещать его по экрану, новые координаты ставить по `e.pageX/pageY` с учетом изначального сдвига элемента относительно курсора.</li>
|
||||||
|
<li>Сообщать во внешний код о текущем `droppable` под курсором и позиции над ним вызовами `onDragEnter`, `onDragMove`, `onDragLeave`.</li>
|
||||||
</ol>
|
</ol>
|
||||||
</li>
|
</li>
|
||||||
<li>При `mouseup` обработать завершение переноса. Элемент под аватаром получить по координатам, предварительно спрятав аватар.</li>
|
<li>При `mouseup` обработать завершение переноса. Элемент под аватаром получить по координатам, предварительно спрятав аватар. Сообщить во внешний код вызовом `onDragEnd`.</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
Получившийся попутно с обсуждением технических моментов Drag'n'Drop фреймворк обладает рядом особенностей:
|
Получившаяся реализация Drag'n'Drop проста, эффективна, изящна.
|
||||||
|
|
||||||
[compare]
|
Её очень легко поменять или адаптировать под "особые" потребности.
|
||||||
+Он прост. Он действительно очень прост.
|
|
||||||
+Он полностью кросс-браузерный, не содержит хаков.
|
|
||||||
+Он позволяет работать с большим количеством потенциальных `draggable`/`droppable`.
|
|
||||||
+Его легко расширить и поменять.
|
|
||||||
[/compare]
|
|
||||||
|
|
||||||
Вы можете получить файлы и посмотреть итоговое демо [edit src="dragDemo"]в песочнице[/edit].
|
|
||||||
|
|
||||||
|
|
||||||
В зависимости от ваших задач, вы можете либо использовать его как отправную точку, либо реализовать свой.
|
|
||||||
|
|
||||||
ООП-вариант фреймворка находится в статье [](/drag-and-drop-plus).
|
ООП-вариант фреймворка находится в статье [](/drag-and-drop-plus).
|
||||||
[head]
|
|
||||||
<script>
|
|
||||||
function getElementUnderClientXY(elem, clientX, clientY) {
|
|
||||||
var display = elem.style.display || '';
|
|
||||||
elem.style.display = 'none';
|
|
||||||
|
|
||||||
var target = document.elementFromPoint(clientX, clientY);
|
|
||||||
|
|
||||||
elem.style.display = display;
|
|
||||||
|
|
||||||
if (!target || target == document) {
|
|
||||||
target = document.body;
|
|
||||||
}
|
|
||||||
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
[/head]
|
|
||||||
[libs]
|
|
||||||
getCoords.js
|
|
||||||
[/libs]
|
|
0
2-ui/3-event-details/7-drag-and-drop-objects/between.png → 2-ui/3-event-details/5-drag-and-drop-objects/between.png
Executable file → Normal file
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
|
@ -1,4 +1,4 @@
|
||||||
var dragManager = new function() {
|
var DragManager = new function() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* составной объект для хранения информации о переносе:
|
* составной объект для хранения информации о переносе:
|
||||||
|
@ -17,7 +17,7 @@ var dragManager = new function() {
|
||||||
|
|
||||||
if (e.which != 1) return;
|
if (e.which != 1) return;
|
||||||
|
|
||||||
var elem = findDraggable(e);
|
var elem = e.target.closest('.draggable');
|
||||||
if (!elem) return;
|
if (!elem) return;
|
||||||
|
|
||||||
dragObject.elem = elem;
|
dragObject.elem = elem;
|
||||||
|
@ -118,23 +118,22 @@ var dragManager = new function() {
|
||||||
avatar.style.position = 'absolute';
|
avatar.style.position = 'absolute';
|
||||||
}
|
}
|
||||||
|
|
||||||
function findDraggable(event) {
|
|
||||||
var elem = event.target;
|
|
||||||
while(elem != document && elem.getAttribute('draggable') == null) {
|
|
||||||
elem = elem.parentNode;
|
|
||||||
}
|
|
||||||
return elem == document ? null : elem;
|
|
||||||
}
|
|
||||||
|
|
||||||
function findDroppable(event) {
|
function findDroppable(event) {
|
||||||
|
// спрячем переносимый элемент
|
||||||
|
dragObject.avatar.hidden = true;
|
||||||
|
|
||||||
var elem = getElementUnderClientXY(dragObject.avatar, event.clientX, event.clientY);
|
// получить самый вложенный элемент под курсором мыши
|
||||||
|
var elem = document.elementFromPoint(event.clientX, event.clientY);
|
||||||
|
|
||||||
while(elem != document && elem.getAttribute('droppable') == null) {
|
// показать переносимый элемент обратно
|
||||||
elem = elem.parentNode;
|
dragObject.avatar.hidden = false;
|
||||||
|
|
||||||
|
if (elem == null) {
|
||||||
|
// такое возможно, если курсор мыши "вылетел" за границу окна
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return elem == document ? null : elem;
|
return elem.closest('.droppable');
|
||||||
}
|
}
|
||||||
|
|
||||||
document.onmousemove = onMouseMove;
|
document.onmousemove = onMouseMove;
|
||||||
|
@ -145,3 +144,15 @@ var dragManager = new function() {
|
||||||
this.onDragCancel = function(dragObject) { };
|
this.onDragCancel = function(dragObject) { };
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function getCoords(elem) { // кроме IE8-
|
||||||
|
var box = elem.getBoundingClientRect();
|
||||||
|
|
||||||
|
return {
|
||||||
|
top: box.top + pageYOffset,
|
||||||
|
left: box.left + pageXOffset
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -9,3 +9,7 @@
|
||||||
.computer-smile {
|
.computer-smile {
|
||||||
background: url(//js.cx/clipart/computer-smile.gif) no-repeat;
|
background: url(//js.cx/clipart/computer-smile.gif) no-repeat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.draggable {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<script src="http://polyfill.webservices.ft.com/v1/polyfill.js?features=Element.prototype.closest"></script>
|
||||||
|
<script src="DragManager.js"></script>
|
||||||
|
<link rel="stylesheet" href="dragDemo.css">
|
||||||
|
<script>
|
||||||
|
DragManager.onDragCancel = function(dragObject) {
|
||||||
|
dragObject.avatar.rollback();
|
||||||
|
};
|
||||||
|
|
||||||
|
DragManager.onDragEnd = function(dragObject, dropElem) {
|
||||||
|
dragObject.elem.style.display = 'none';
|
||||||
|
dropElem.classList.add('computer-smile');
|
||||||
|
setTimeout(function() {
|
||||||
|
dropElem.classList.remove('computer-smile');
|
||||||
|
}, 200);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div style="height:64px">
|
||||||
|
<img src="//js.cx/browsers/chrome.svg" class="draggable">
|
||||||
|
<img src="//js.cx/browsers/firefox.svg" class="draggable">
|
||||||
|
<img src="//js.cx/browsers/ie.svg" class="draggable">
|
||||||
|
<img src="//js.cx/browsers/opera.svg" class="draggable">
|
||||||
|
<img src="//js.cx/browsers/safari.svg" class="draggable">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>Браузер переносить сюда:</p>
|
||||||
|
|
||||||
|
<div class="computer droppable">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
0
2-ui/3-event-details/7-drag-and-drop-objects/shiftx.png → 2-ui/3-event-details/5-drag-and-drop-objects/shiftx.png
Executable file → Normal file
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
@ -1,46 +0,0 @@
|
||||||
# HTML/CSS, подсказка
|
|
||||||
|
|
||||||
Слайдер -- это `DIV`, подкрашенный фоном/градиентом, внутри которого находится другой `DIV`, оформленный как бегунок, с `position:relative`.
|
|
||||||
|
|
||||||
Бегунок немного поднят, и вылезает по высоте из родителя.
|
|
||||||
|
|
||||||
# HTML/CSS для слайдера
|
|
||||||
|
|
||||||
Например, вот так:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<!--+ run -->
|
|
||||||
<style>
|
|
||||||
.slider {
|
|
||||||
border-radius: 5px;
|
|
||||||
background: #E0E0E0;
|
|
||||||
background: -moz-linear-gradient(left top , #E0E0E0, #EEEEEE) repeat scroll 0 0 transparent;
|
|
||||||
background: -webkit-gradient(linear, left top, right bottom, from(#E0E0E0), to(#EEEEEE));
|
|
||||||
background: linear-gradient(left top, #E0E0E0, #EEEEEE);
|
|
||||||
width: 310px;
|
|
||||||
height: 15px;
|
|
||||||
margin: 5px;
|
|
||||||
}
|
|
||||||
.thumb {
|
|
||||||
width: 10px;
|
|
||||||
height: 25px;
|
|
||||||
border-radius: 3px;
|
|
||||||
position: relative;
|
|
||||||
left: 10px;
|
|
||||||
top: -5px;
|
|
||||||
background: blue;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div class="slider">
|
|
||||||
<div class="thumb"></div>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
Теперь на этом реализуйте перенос бегунка.
|
|
||||||
|
|
||||||
# Полное решение
|
|
||||||
|
|
||||||
|
|
||||||
Это горизонтальный Drag'n'Drop, ограниченный по ширине. Его особенность -- в `position:relative` у переносимого элемента, т.е. координата ставится не абсолютная, а относительно родителя.
|
|
|
@ -1,44 +0,0 @@
|
||||||
|
|
||||||
function fixEvent(e) {
|
|
||||||
e = e || window.event;
|
|
||||||
|
|
||||||
if (!e.target) e.target = e.srcElement;
|
|
||||||
|
|
||||||
if (e.pageX == null && e.clientX != null ) { // если нет pageX..
|
|
||||||
var html = document.documentElement;
|
|
||||||
var body = document.body;
|
|
||||||
|
|
||||||
e.pageX = e.clientX + (html.scrollLeft || body && body.scrollLeft || 0);
|
|
||||||
e.pageX -= html.clientLeft || 0;
|
|
||||||
|
|
||||||
e.pageY = e.clientY + (html.scrollTop || body && body.scrollTop || 0);
|
|
||||||
e.pageY -= html.clientTop || 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!e.which && e.button) {
|
|
||||||
e.which = e.button & 1 ? 1 : ( e.button & 2 ? 3 : ( e.button & 4 ? 2 : 0 ) )
|
|
||||||
}
|
|
||||||
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function getCoords(elem) {
|
|
||||||
var box = elem.getBoundingClientRect();
|
|
||||||
|
|
||||||
var body = document.body;
|
|
||||||
var docElem = document.documentElement;
|
|
||||||
|
|
||||||
var scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop;
|
|
||||||
var scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft;
|
|
||||||
|
|
||||||
var clientTop = docElem.clientTop || body.clientTop || 0;
|
|
||||||
var clientLeft = docElem.clientLeft || body.clientLeft || 0;
|
|
||||||
|
|
||||||
var top = box.top + scrollTop - clientTop;
|
|
||||||
var left = box.left + scrollLeft - clientLeft;
|
|
||||||
|
|
||||||
return { top: Math.round(top), left: Math.round(left) };
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<script src="lib.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<!-- Ваш HTML/CSS -->
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// ваш код
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
|
|
||||||
function fixEvent(e) {
|
|
||||||
e = e || window.event;
|
|
||||||
|
|
||||||
if (!e.target) e.target = e.srcElement;
|
|
||||||
|
|
||||||
if (e.pageX == null && e.clientX != null ) { // если нет pageX..
|
|
||||||
var html = document.documentElement;
|
|
||||||
var body = document.body;
|
|
||||||
|
|
||||||
e.pageX = e.clientX + (html.scrollLeft || body && body.scrollLeft || 0);
|
|
||||||
e.pageX -= html.clientLeft || 0;
|
|
||||||
|
|
||||||
e.pageY = e.clientY + (html.scrollTop || body && body.scrollTop || 0);
|
|
||||||
e.pageY -= html.clientTop || 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!e.which && e.button) {
|
|
||||||
e.which = e.button & 1 ? 1 : ( e.button & 2 ? 3 : ( e.button & 4 ? 2 : 0 ) )
|
|
||||||
}
|
|
||||||
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function getCoords(elem) {
|
|
||||||
var box = elem.getBoundingClientRect();
|
|
||||||
|
|
||||||
var body = document.body;
|
|
||||||
var docElem = document.documentElement;
|
|
||||||
|
|
||||||
var scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop;
|
|
||||||
var scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft;
|
|
||||||
|
|
||||||
var clientTop = docElem.clientTop || body.clientTop || 0;
|
|
||||||
var clientLeft = docElem.clientLeft || body.clientLeft || 0;
|
|
||||||
|
|
||||||
var top = box.top + scrollTop - clientTop;
|
|
||||||
var left = box.left + scrollLeft - clientLeft;
|
|
||||||
|
|
||||||
return { top: Math.round(top), left: Math.round(left) };
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -1,80 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
document.body.onmousedown = function(e) {
|
|
||||||
|
|
||||||
var dragElement = e.target;
|
|
||||||
|
|
||||||
if (!dragElement.classList.contains('draggable')) return;
|
|
||||||
|
|
||||||
var coords, shiftX, shiftY;
|
|
||||||
|
|
||||||
startDrag(e.pageX, e.pageY);
|
|
||||||
|
|
||||||
document.onmousemove = function(e) {
|
|
||||||
moveAt(e.pageX, e.pageY);
|
|
||||||
};
|
|
||||||
|
|
||||||
dragElement.onmouseup = function() {
|
|
||||||
finishDrag();
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// -------------------------
|
|
||||||
|
|
||||||
function startDrag(pageX, pageY) {
|
|
||||||
|
|
||||||
coords = getCoords(dragElement);
|
|
||||||
shiftX = pageX - coords.left;
|
|
||||||
shiftY = pageY - coords.top;
|
|
||||||
|
|
||||||
dragElement.classList.add("dragging");
|
|
||||||
dragElement.style.position = 'absolute';
|
|
||||||
document.body.appendChild(dragElement);
|
|
||||||
|
|
||||||
moveAt(pageX, pageY);
|
|
||||||
};
|
|
||||||
|
|
||||||
function finishDrag() {
|
|
||||||
dragElement.classList.remove('dragging');
|
|
||||||
document.onmousemove = dragElement.onmouseup = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function moveAt(pageX, pageY) {
|
|
||||||
var newX = pageX - shiftX;
|
|
||||||
var newY = pageY - shiftY;
|
|
||||||
|
|
||||||
var newBottom = newY + dragElement.offsetHeight;
|
|
||||||
|
|
||||||
var docScroll = getDocumentScroll();
|
|
||||||
|
|
||||||
// прокрутить вниз, если нужно
|
|
||||||
if (newBottom > docScroll.bottom) {
|
|
||||||
// ...но не за конец документа
|
|
||||||
var scrollSizeToEnd = docScroll.height - docScroll.bottom;
|
|
||||||
|
|
||||||
// scrollBy, если его не ограничить,
|
|
||||||
// может заскроллить за текущую границу страницы
|
|
||||||
var toScrollY = Math.min(scrollSizeToEnd, 10);
|
|
||||||
window.scrollBy(0, toScrollY );
|
|
||||||
|
|
||||||
// при необходимости двигаем элемент вверх, чтобы поместился
|
|
||||||
// метод scrollBy асинхронный, поэтому учитываем будущую прокрутку (+toScrollY)
|
|
||||||
newY = Math.min(newY, docScroll.bottom - dragElement.offsetHeight + toScrollY);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newY < docScroll.top) {
|
|
||||||
var toScrollY = Math.min(docScroll.top, 10);
|
|
||||||
window.scrollBy(0, -toScrollY );
|
|
||||||
newY = Math.max(newY, docScroll.top - toScrollY );
|
|
||||||
}
|
|
||||||
|
|
||||||
// зажать в границах экрана по горизонтали
|
|
||||||
newX = Math.max(newX, 0);
|
|
||||||
newX = Math.min(newX, document.documentElement.clientWidth - dragElement.offsetHeight);
|
|
||||||
|
|
||||||
dragElement.style.left = newX + 'px';
|
|
||||||
dragElement.style.top = newY + 'px';
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
|
@ -1,2 +0,0 @@
|
||||||
|
|
||||||
/* ваш код */
|
|
|
@ -1,13 +0,0 @@
|
||||||
# Расставить супергероев по полю
|
|
||||||
|
|
||||||
[importance 5]
|
|
||||||
|
|
||||||
Сделайте так, чтобы элементы с классом `draggable` можно было переносить мышкой.
|
|
||||||
|
|
||||||
Футбольное поле в этой задач слишком большое, чтобы показывать его здесь, поэтому откройте его, кликнув по ссылке ниже. Там же и подробное описание задачи (осторожно, винни-пух и супергерои!).
|
|
||||||
|
|
||||||
[demo src="solution"]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
P.S. Для вашего удобства добавлены функции `getCoords` -- для координат и `getDocumentScroll` -- для получения границ видимой области и прокрутки в документе.
|
|
|
@ -13,11 +13,11 @@
|
||||||
<li>А событие `wheel` является чисто "мышиным". Оно генерируется *над любым элементом* при передвижении колеса мыши. При этом не важно, прокручиваемый он или нет. В частности, `overflow:hidden` никак не препятствует обработке колеса мыши.</li>
|
<li>А событие `wheel` является чисто "мышиным". Оно генерируется *над любым элементом* при передвижении колеса мыши. При этом не важно, прокручиваемый он или нет. В частности, `overflow:hidden` никак не препятствует обработке колеса мыши.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
Кроме того, событие `onscroll` происходит после прокрутки, а `onwheel` -- до прокрутки, поэтому в нём можно отменить саму прокрутку (действие браузера).
|
Кроме того, событие `onscroll` происходит *после* прокрутки, а `onwheel` -- *до* прокрутки, поэтому в нём можно отменить саму прокрутку (действие браузера).
|
||||||
|
|
||||||
## Зоопарк wheel в разных браузерах
|
## Зоопарк wheel в разных браузерах
|
||||||
|
|
||||||
Событие `wheel` появилось в [стандарте](http://www.w3.org/TR/DOM-Level-3-Events/#event-type-wheel) не так давно. Оно поддерживается IE9+, Firefox 17+. Возможно, другими браузерами на момент чтения этой статьи.
|
Событие `wheel` появилось в [стандарте](http://www.w3.org/TR/DOM-Level-3-Events/#event-type-wheel) не так давно. Оно поддерживается Chrome 31+, IE9+, Firefox 17+.
|
||||||
|
|
||||||
До него браузеры обрабатывали прокрутку при помощи событий [mousewheel](http://msdn.microsoft.com/en-us/library/ie/ms536951.aspx) (все кроме Firefox) и [DOMMouseScroll](https://developer.mozilla.org/en-US/docs/DOM/DOM_event_reference/DOMMouseScroll), [MozMousePixelScroll](https://developer.mozilla.org/en-US/docs/DOM/DOM_event_reference/MozMousePixelScroll) (только Firefox).
|
До него браузеры обрабатывали прокрутку при помощи событий [mousewheel](http://msdn.microsoft.com/en-us/library/ie/ms536951.aspx) (все кроме Firefox) и [DOMMouseScroll](https://developer.mozilla.org/en-US/docs/DOM/DOM_event_reference/DOMMouseScroll), [MozMousePixelScroll](https://developer.mozilla.org/en-US/docs/DOM/DOM_event_reference/MozMousePixelScroll) (только Firefox).
|
||||||
|
|
||||||
|
@ -27,26 +27,24 @@
|
||||||
<dd>Свойство `deltaY` -- количество прокрученных пикселей по горизонтали и вертикали. Существуют также свойства `deltaX` и `deltaZ` для других направлений прокрутки.</dd>
|
<dd>Свойство `deltaY` -- количество прокрученных пикселей по горизонтали и вертикали. Существуют также свойства `deltaX` и `deltaZ` для других направлений прокрутки.</dd>
|
||||||
<dt>`MozMousePixelScroll`</dt>
|
<dt>`MozMousePixelScroll`</dt>
|
||||||
<dd>Срабатывает, начиная с Firefox 3.5, только в Firefox. Даёт возможность отменить прокрутку и получить размер в пикселях через свойство `detail`, ось прокрутки в свойстве `axis`.</dd>
|
<dd>Срабатывает, начиная с Firefox 3.5, только в Firefox. Даёт возможность отменить прокрутку и получить размер в пикселях через свойство `detail`, ось прокрутки в свойстве `axis`.</dd>
|
||||||
<dt>`DOMMouseScroll`</dt>
|
|
||||||
<dd>Существует в Firefox очень давно, отличается от предыдущего тем, что даёт в `detail` количество строк. Если не нужна поддержка Firefox < 3.5, то не нужно и это событие.</dd>
|
|
||||||
<dt>`mousewheel`</dd>
|
<dt>`mousewheel`</dd>
|
||||||
<dd>Срабатывает в браузерах, которые ещё не реализовали `wheel`. В свойстве `wheelDelta` -- условный "размер прокрутки", обычно равен `120` для прокрутки вверх и `-120` -- вниз. Он не соответствует какому-либо конкретному количеству пикселей.</dd>
|
<dd>Срабатывает в браузерах, которые ещё не реализовали `wheel`. В свойстве `wheelDelta` -- условный "размер прокрутки", обычно равен `120` для прокрутки вверх и `-120` -- вниз. Он не соответствует какому-либо конкретному количеству пикселей.</dd>
|
||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
Чтобы кросс-браузерно отловить прокрутку и, при необходимости, отменить её, можно использовать все эти события.
|
Чтобы кросс-браузерно отловить прокрутку и, при необходимости, отменить её, можно использовать все эти события.
|
||||||
|
|
||||||
Пример:
|
Пример, включающий поддержку IE8-:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
if (elem.addEventListener) {
|
if (elem.addEventListener) {
|
||||||
if ('onwheel' in document) {
|
if ('onwheel' in document) {
|
||||||
// IE9+, FF17+
|
// IE9+, FF17+, Ch31+
|
||||||
elem.addEventListener ("wheel", onWheel, false);
|
elem.addEventListener ("wheel", onWheel, false);
|
||||||
} else if ('onmousewheel' in document) {
|
} else if ('onmousewheel' in document) {
|
||||||
// устаревший вариант события
|
// устаревший вариант события
|
||||||
elem.addEventListener ("mousewheel", onWheel, false);
|
elem.addEventListener ("mousewheel", onWheel, false);
|
||||||
} else {
|
} else {
|
||||||
// 3.5 <= Firefox < 17, более старое событие DOMMouseScroll пропустим
|
// Firefox < 17
|
||||||
elem.addEventListener ("MozMousePixelScroll", onWheel, false);
|
elem.addEventListener ("MozMousePixelScroll", onWheel, false);
|
||||||
}
|
}
|
||||||
} else { // IE<9
|
} else { // IE<9
|
0
2-ui/3-event-details/4-mousewheel/wheel.view/index.html → 2-ui/3-event-details/6-mousewheel/wheel.view/index.html
Executable file → Normal file
|
@ -1,35 +0,0 @@
|
||||||
<!DOCTYPE HTML>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<script src="lib.js"></script>
|
|
||||||
<script src="DragManager.js"></script>
|
|
||||||
<link rel="stylesheet" type="text/css" href="dragDemo.css">
|
|
||||||
<script>
|
|
||||||
dragManager.onDragCancel = function(dragObject) {
|
|
||||||
dragObject.avatar.rollback();
|
|
||||||
};
|
|
||||||
|
|
||||||
dragManager.onDragEnd = function(dragObject, dropElem) {
|
|
||||||
dropElem.className = 'computer computer-smile';
|
|
||||||
dragObject.elem.style.display = 'none';
|
|
||||||
setTimeout(function() { dropElem.className = 'computer'; }, 200);
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<img src="//js.cx/browsers/chrome_64.png" draggable>
|
|
||||||
<img src="//js.cx/browsers/firefox_64.png" draggable>
|
|
||||||
<img src="//js.cx/browsers/ie_64.png" draggable>
|
|
||||||
<img src="//js.cx/browsers/opera_64.png" draggable>
|
|
||||||
<img src="//js.cx/browsers/safari_64.png" draggable>
|
|
||||||
|
|
||||||
<p>Браузер переносить сюда:</p>
|
|
||||||
|
|
||||||
<div class="computer" droppable>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,30 +0,0 @@
|
||||||
|
|
||||||
function getCoords(elem) {
|
|
||||||
var box = elem.getBoundingClientRect();
|
|
||||||
|
|
||||||
var body = document.body;
|
|
||||||
var docElem = document.documentElement;
|
|
||||||
|
|
||||||
var scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop;
|
|
||||||
var scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft;
|
|
||||||
|
|
||||||
var clientTop = docElem.clientTop || body.clientTop || 0;
|
|
||||||
var clientLeft = docElem.clientLeft || body.clientLeft || 0;
|
|
||||||
|
|
||||||
var top = box.top + scrollTop - clientTop;
|
|
||||||
var left = box.left + scrollLeft - clientLeft;
|
|
||||||
|
|
||||||
return { top: Math.round(top), left: Math.round(left) };
|
|
||||||
}
|
|
||||||
|
|
||||||
function getElementUnderClientXY(elem, clientX, clientY) {
|
|
||||||
var display = elem.style.display || '';
|
|
||||||
elem.style.display = 'none';
|
|
||||||
|
|
||||||
var target = document.elementFromPoint(clientX, clientY);
|
|
||||||
|
|
||||||
elem.style.display = display;
|
|
||||||
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
|
|
||||||
|
|
||||||
<div id="field">
|
<div id="field">
|
||||||
<img src="//js.cx/clipart/ball.gif" id="ball">
|
<img src="//js.cx/clipart/ball.svg" id="ball">
|
||||||
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
|
|
||||||
<div id="field">
|
<div id="field">
|
||||||
<img src="//js.cx/clipart/ball.gif" id="ball">
|
<img src="//js.cx/clipart/ball.svg" id="ball">
|
||||||
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
|
|
||||||
<div id="field">
|
<div id="field">
|
||||||
<img src="//js.cx/clipart/ball.gif" width="40" height="40" id="ball">
|
<img src="//js.cx/clipart/ball.svg" width="40" height="40" id="ball">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
|
|
||||||
|
|
||||||
<div id="field">
|
<div id="field">
|
||||||
<img src="//js.cx/clipart/ball.gif" width="40" height="40" id="ball">
|
<img src="//js.cx/clipart/ball.svg" width="40" height="40" id="ball">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
|
|
||||||
<div id="field">
|
<div id="field">
|
||||||
<img src="//js.cx/clipart/ball.gif" width="40" height="40" id="ball">
|
<img src="//js.cx/clipart/ball.svg" width="40" height="40" id="ball">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
|
|
||||||
|
|
||||||
<div id="field">
|
<div id="field">
|
||||||
<img src="//js.cx/clipart/ball.gif" width="40" height="40" id="ball">
|
<img src="//js.cx/clipart/ball.svg" width="40" height="40" id="ball">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
# Применяем ООП: Drag'n'Drop++
|
# Применяем ООП: Drag'n'Drop++
|
||||||
|
|
||||||
Эта статья представляет собой продолжение статьи [](/drag-and-drop-objects). Она посвящена более гибкой и расширяемой реализации переноса.
|
Эта статья представляет собой продолжение главы [](/drag-and-drop-objects).
|
||||||
|
Она посвящена более гибкой и расширяемой реализации переноса.
|
||||||
|
|
||||||
|
Рекомендуется прочитать указанную главу перед тем, как двигаться дальше.
|
||||||
|
|
||||||
[cut]
|
[cut]
|
||||||
В сложных приложениях Drag'n'Drop обладает рядом особенностей:
|
В сложных приложениях Drag'n'Drop обладает рядом особенностей:
|
||||||
<ol>
|
<ol>
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
mongoexport -d js -c plunks --jsonArray >plunks.json
|
|
|
@ -1,2 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
mongoimport -d js -c plunks --jsonArray --drop <plunks.json
|
|