Создайте всплывающую подсказку над элементом.
Подсказка должна появляться при наведении на элемент, по центру и на небольшом расстоянии сверху. При уходе курсора с элемента — исчезать.
Вы можете посмотреть поведение подсказки в ифрейме ниже, наведя курсор на ссылку.
Способ добавления подсказки к элементу:
new Tooltip(elem, "Вот <b>такая</b> подсказка!");
- В подсказке и элементе, на который она поставлена, может быть произвольный HTML. Оформление подсказки должно задаваться CSS.
- Не надо рассматривать случай, когда у вложенного элемента своя подсказка, отдельная от родителя, т.к. такого не бывает.
- Допустимо вылезание подсказки за пределы видимой области, если элемент расположен у верхней границы окна.
- Объект подсказки не должен иметь публичных методов, только приватные.
Исходный документ: tutorial/browser/events/tooltip-fixed-src/index.html.
P.S. Возможно, вам понадобятся функции fixEvent и getCoords. Они приложены к документу в файле lib.js.
P.P.S. Можно сверстать подсказки и при помощи CSS, если элементы заранее известны и их мало. JS-подход более универсален и не зависит от вёрстки.
Для управления подсказкой нам понадобятся два события:
- Наведение на элемент
mouseover - Уход с элемента
mouseout
Скелет кода:
function Tooltip(elem, text) {
var offsetFromElement = 10; // отступ подсказки от элемента 10px
var tooltipElem; // тут будет DOM-элемент для подсказки
elem.onmouseout = function() {
hide(); // спрятать подсказку
};
elem.onmouseover = function() {
show(); // показать подсказку
};
}
Функция show должна при первом показе сгенерировать элемент с подсказкой, а затем — показать в нужном месте страницы.
Для генерации подсказки добавим вспомогательную функцию getTooltipElem(). Она будет возвращать существующий элемент, если он есть, а если нет — генерировать новый.
function getTooltipElem() {
if (!tooltipElem) {
tooltipElem = document.createElement('div');
tooltipElem.className = 'tooltip';
tooltipElem.innerHTML = text;
}
return tooltipElem;
}
Основная настройка вида подсказки будет в CSS-классе tooltip.
Например:
.tooltip {
position:absolute;
left: -9999px; /* за пределами экрана */
z-index:100; /* подсказка должна перекрывать другие элементы */
background: #F0FFF0;
padding: 5px;
border: 1px dashed green;
text-align: center;
}
Как правильно отпозиционировать подсказку? Для начала, по горизонтали.
Центр подсказки должен быть ровно над центром элемента, который имеет координату getCoords(elem).left + elem.offsetWidth/2.
Если поставить tooltipElem.left в это значение — результат будет выглядеть так:

Дополнительно нужно сдвинуть подсказку на половину собственной ширины влево:

Теперь отпозиционируем по вертикали.
Это гораздо проще: нужно взять координату getCoords(elem).top и вычесть из неё высоту подсказки и дополнительный отступ:

Для того, чтобы определить высоту подсказки offsetHeight, она должна быть в DOM со свойством display, не равным none.
Поэтому мы позиционируем её за пределами видимой области, поставив left:-9999px.
В качестве альтренативы можно было бы использовать для скрытия visibility:hidden. Это свойство, в отличие от display:none, оставляет элементу размеры, просто делает его невидимым.
Полный код решения демонстрирует эти подходы на tutorial/browser/events/tooltip-fixed/index.html.
Создайте всплывающую подсказку, следующую за курсором.
- Подсказка должна появляться при наведении на элемент, на небольшом расстоянии справа-снизу от курсора.
- При передвижении курсора подсказка следует за ним.
- Если курсор слишком низко/справа, то чтобы подсказка не вылезла за нижнюю/правую границу экрана — пусть отображается сверху/слева от курсора.
Вы можете посмотреть поведение подсказки в ифрейме ниже, наведя курсор на правый-нижний угол.
Способ добавления подсказки к элементу:
new Tooltip(elem, "Вот <b>такая</b> подсказка!");
Естественные пожелания:
- Подсказка не должна «моргать» при движении мыши.
- Подсказка должна правильно работать, если у страницы есть прокрутка.
- В подсказке и элементе, на который она поставлена, может быть произвольный HTML. Оформление подсказки должно задаваться CSS.
- Не надо рассматривать случай, когда у вложенного элемента своя подсказка, отдельная от родителя, т.к. такого не бывает.
- Объект подсказки не должен иметь публичных методов, только приватные.
Исходный документ: tutorial/browser/events/tooltip-moving-src/index.html.
К нему подключён файл с функцией fixEvent.
Три события имеют отношение к подсказке:
- При
mouseover— показать. - При
mousemove— показать на новом месте. - При
mouseout— спрятать.
…Но при заходе на элемент происходят сразу оба события: mouseover и mousemove на нём же. Зачем вызывать код для показа два раза? Можно оставить его только в mousemove.
Стиль элемента подсказки:
.tooltip {
position:absolute;
left: -9999px; /* изначально спрятана за пределы экрана */
z-index:100; /* подсказка должна перекрывать другие элементы */
...
}
Скелет кода:
function Tooltip(elem, text) {
var offsetFromCursor = 10; // отступ от курсора 10px
var tooltipElem; // тут будет элемент-подсказка
elem.onmouseout = function() {
hide(); // спрятать подсказку
};
elem.onmousemove = function(e) {
e = fixEvent(e);
show(e.pageX, e.pageY); // показать рядом с курсором
};
function getTooltipElem() { // создать элемент-подсказку
if (!tooltipElem) {
tooltipElem = document.createElement('div');
tooltipElem.className = 'tooltip'; // CSS подсказки
tooltipElem.innerHTML = text;
}
return tooltipElem;
}
function hide() {
document.body.removeChild(getTooltipElem());
};
function show(pageX, pageY) {
показать getTooltipElem() для координат курсора pageX/pageY
}
}
Для показа на нужном месте — как правило, подойдут простые вычисления. Функция show ниже сдвигает элемент на offsetFromCursor относительно переданных её координат курсора:
function show(pageX, pageY) {
var tooltipElem = getTooltipElem();
var newLeft = pageX + offsetFromCursor;
var newTop = pageY + offsetFromCursor;
tooltipElem.left = newLeft + 'px';
tooltipElem.top = newTop + 'px';
}
Основной сложный момент здесь — определить, когда подсказка вылезает за правую/нижнюю границы окна. Для этого нужно эти границы вычислить:
var scrollY = window.pageYOffset || document.documentElement.scrollTop; var winBottom = scrollY + document.documentElement.clientHeight; var scrollX = window.pageXOffset || document.documentElement.scrollLeft; var winRight = scrollX + document.documentElement.clientWidth;
Соответствующие свойства, использованные для вычисления, описаны в статье Размеры и прокрутка элементов.
Проверим, не вылезет ли подсказка за границы, и если это так — перенесём ее:
if (newLeft + tooltipElem.offsetWidth > winRight) {
// правая граница подсказки вылезает за экран
newLeft -= tooltipElem.offsetWidth;
newLeft -= offsetFromCursor+2; // (*)
}
if (newTop + tooltipElem.offsetHeight > winBottom) {
// нижняя граница подсказки вылезает за экран
newTop -= tooltipElem.offsetHeight;
newTop -= offsetFromCursor+2; // (**)
}
Здесь есть еще две тонкости.
- Для того, чтобы можно было получить
offsetHeight/offsetWidth, подсказка должна быть скрыта при помощиvisibility:hiddenилиleft/topза пределами экрана, но неdisplay:none. - В строках
(*)и(**)подсказку сдвигаем немного левее/выше курсора (на 2 пикселя).Главное чтобы подсказка не оказалась под курсором. Если такое произойдет, то подсказка перекроет элемент, и в результате события
mousemoveстанут происходить уже на подсказке, а не на элементе, и их обработка сломается.
Полное решение: tutorial/browser/events/tooltip-moving/index.html.
Реализуйте интерфейс для смены размера изображения мышью.
Пусть изображение «тянется» при захвате его снизу, справа и в правом-нижнем углу, вот так:
Открыть в новом окне
По окончанию смены размеров должно быть событие resize c новыми размерами.
Синтаксис:
var resizeMe = new Resizeable(document.getElementById('heroes'));
resizeMe.on("resize", function(newWidth, newHeight) {
// вывести изменения
var info = "ширина:" + newWidth + ", высота:" + newHeight;
document.getElementById('info').innerHTML = info;
});
Минимальный размер изображения 3x3, меньше ресайзить нельзя.
В исходном документе tutorial/browser/events/resizeable-src/index.html находится картинка handle-se.png для правого-нижнего угла и dom.js, eventer.js с вспомогательными функциями.
Для захвата проще всего создать дополнительные DIV'ы и отпозиционировать их сбоку и справа-снизу IMG. При нажатии на них начинать смену размера.
CSS-структура:
<div class="resize-wrapper" style="width: 503px; height: 285px;"> <img src="heroes.jpg" id="heroes" style="width:500px;height:282px"> <div class="resize-handle-s"></div> <div class="resize-handle-e"></div> <div class="resize-handle-se"></div> </div>
Внешний DIV подстраивается под размер картинки и имеет position:relative. Внутри него расположены абсолютно позиционированные «ручки» для захвата.
Стиль:
.resize-wrapper {
position: relative;
}
.resize-wrapper img {
display: block;
}
.resize-handle-se {
width: 16px;
height: 16px;
position: absolute;
bottom: 0;
right: 0;
background: url(handle-se.png) no-repeat;
cursor: se-resize;
}
.resize-handle-s {
height: 3px;
width:100%;
position: absolute;
bottom: 0;
background: gray;
cursor: s-resize;
}
.resize-handle-e {
width: 3px;
height:100%;
position: absolute;
right: 0;
top: 0;
background: gray;
cursor: e-resize;
}
Для обозначения низа используется буква «s» (south, «юг» по-английски), обозначения правой стороны — буква «e» (east, «восток» по-английски).
Использование сторон света для обозначения направления можно часто встретить в скриптах.
Алгоритм:
- При инициализации IMG оборачивается во внешнюю обертку и обкладывается
DIV'ами, на которых ловимmousedown. Обертка нужна, чтобы абсолютно позиционироватьDIV'ывнутри неё под/сбоку изображения. - При наступлении
mousedown, начинаем ловитьdocument.mousemoveи подгонять картинку под размер, а обертку — под картинку.Желательно заодно отменить браузерное выделение и Drag’n’Drop, возвратив
falseиз собработчиков событийmousedownиdragstart. - При наступлении
mouseup— конец.
Более подробно детали отмечены в комментариях к решению: tutorial/browser/events/resizeable/index.html
Реализуйте интерфейс для выбора фрагмента изображения мышью.
Нажмите на изображении и проведите мышью для выбора:
Открыть в новом окне
По окончанию смены размеров должно быть событие crop c left/top координатами и width/height размерами выбранной области.
Синтаксис:
var croppable = new Croppable(document.getElementById('heroes'));
croppable.on("crop", function(dims) {
// вывести координаты и размеры crop-квадрата относительно изображения
});
- Область можно выбирать, двигая курсором в любую сторону от исходной точки.
- Область не может вылезать за пределы изображения.
В исходном документе tutorial/browser/events/croppable-src/index.html находится картинка и dom.js, eventer.js с вспомогательными функциями.
Область выделения — это DIV, серого цвета, полупрозрачный, с рамкой:
.crop-area {
position: absolute;
background: #F5DEB3;
opacity: 0.3;
filter:alpha(opacity=30); /* IE opacity */
border: 1px dashed black;
}
Решение: tutorial/browser/events/croppable/index.html
Обратите внимание: обработчики mousemove/mouseup ставятся на document, не на элемент.
Это для того, чтобы посетитель мог начать выделение на элементе, а продолжить и завершить его, двигая зажатой мышкой снаружи, вне его границ.
Создайте раскрывающееся по наведению меню:
Меню должно нормально реагировать:
- При движении курсора внутри себя, наведении на ссылки.
- При наведении курсора на меню, с меню и затем обратно (скрытие-раскрытие).
При клике меню должно инициировать событие, на которое можно повесить обработчик.
var menu = new SlidingMenu({
elem: document.getElementById('sweeties')
});
menu.on("select", function(value) {
document.getElementById("selected").innerHTML = value;
});
В исходном документе tutorial/browser/animation/sliding-hover-src/index.html находится DOM-структура для меню. К заданию прикреплён файл с функцией animate, delta-функциями и библиотека событий eventer.
Решение: .
На основе слайдера из задачи Слайдер создайте графический компонент, который умеет возвращать/получать значение.
Синтаксис:
var slider = new Slider({
elem: document.getElementById('slider'),
max: 100 // слайдер на самой правой позиции соответствует 100
});
Метод setValue устанавливает значение:
slider.setValue(50);
У слайдера должно быть два события: slide при каждом передвижении и change при отпускании мыши (установке значения).
Пример использования:
slider.on("slide", function(value) {
document.getElementById('slide').innerHTML = value;
});
slider.on("change", function(value) {
document.getElementById('change').innerHTML = value;
});
В действии:
- Дизайн слайдера, ширина/высота элементов должна быть изменяемой через CSS, без необходимости трогать JS-код.
- Центр бегунка должен располагаться над значением. Например, он должен быть в центре для 50 при максимуме 100.
Исходный документ — работающий слайдер из задачи Слайдер : tutorial/browser/events/slider-src/index.html.
Как сопоставить позицию слайдера и значение?
Для этого посмотрим крайние значения слайдера. Допустим, размер бегунка 10px.
Раз центр соответствует значению, то крайнее левое значение будет соответствовать центру на 5px, а крайнее правой — центру на 5px от правой границы:

Соответственно, ширина области изменения будет sliderElem.clientWidth - thumbElem.clientWidth. Далее её можно уже поделить на части, количество пикселей на значение будет:
pixelsPerValue = (sliderElem.clientWidth-thumbElem.clientWidth) / max;
Может получиться так, что это значение будет дробным, меньше единицы. Например, если max = 1000, а ширина слайдера 110 (пробег 100), то будет 0.1 пикселя на значение.
Используя pixelsPerValue мы сможем переводить позицию бегунка в значение и обратно.
Крайнее левое значение thumbElem.style.left равно нулю, крайнее правой — как раз ширине доступной области sliderElem.clientWidth - thumbElem.clientWidth. Поэтому можно получив значение, поделив его на pixelsPerValue:
value = Math.round( newLeft / pixelsPerValue);
Итоговое решение: tutorial/browser/events/slider/index.html.
Создайте виджет окна для чата.
Окно — это DIV, который можно переносить, взявшись за заголовок. По нажатию на «Послать» данные передаются в содержание окна.
Посмотреть пример в новом окне.
Синтаксис:
new DraggableWindow("Чат с Петей");
- Возможно появление прокрутки внутри окна, если сообщений много. Сообщения не должны вылезать вовне окна.
- Окно нельзя вытащить за пределы экрана, даже резкими движениями мыши. Влево-вправо-вверх оно вообще не должно вылезать за границу, а вниз — только до заголовка. Попробуйте перемещать его в
примере, чтобы увидеть. - Для задания DOM-структуры окна используйте шаблон. Может быть создано несколько окон.
Исходный документ: tutorial/browser/dnd/window-src/index.html
- Так как высота и ширина окна известны, вёрстка внутри может содержать точные пиксельные размеры.
- При обработке события
document.onmousemove, мы вычисляем новые координатыleft/topи смотрим, вылезает ли окно за границы. Если да — меняемleft/topна максимально возможные, чтобы не вылезало. - На форме вешаем обработчик
onsubmit, т.к. иначе Enter в поле отправить её на сервер.
Эта задача является продолжением задачи Переносимые окна.
Предусмотрите возможность создавать несколько окон. При этом то, которое захватывают мышью, или в котором начали печатать, становится выше всех.
Общее управление окнами должен осуществлять объект WindowManager.
Синтаксис:
WindowManager.addWindow("Чат с Петей");
WindowManager.addWindow("Чат с Машей");
Посмотреть пример в новом окне.
- Окна перекрывают друг-друга по
z-index. Максимальныйz-indexдолжен быть ограничен: от 1 до количества окон. - Информация от окна к менеджеру передаётся через события.
В качестве исходного документа можно взять решение предыдущей задачи: tutorial/browser/dnd/window/index.html, добавив к нему eventer.js.
Создайте тройной селектор даты, который выступает как единый компонент. То есть, можно подписываться на "select" сразу у этого компонента.
Конструктор:
var dateSelector = new DateSelector({
yearFrom: 2010, // начальный год в селекторе
yearTo: 2020, // конечный год в селекторе
value: new Date(2012,2,31) // текущая выбранная дата
});
События:
selected— при изменении даты.
Методы:
setValue(date, disableEvent)— устанавливает датуdate. Если второй аргументtrue, то событие не генерируется.getElement()— возвращает DOM-элемент для компоненты для вставки в документ. При необходимости (первый пока) создаёт DOM.
Использование - добавление в документ:
var elem = dateSelector.getElement();
document.getElementById('selector').appendChild(elem);
Использование - подписка на изменение и вывод значения:
dateSelector.on("select", function() {
document.getElementById('value').innerHTML = this.getValue();
});
Пример в действии:
Исходный документ: tutorial/browser/events/dateselector-src/index.html.
P.S. При выборе месяца дни должны подстраваться под него. Чтобы не было доступно 31 февраля.
Создайте календарь.
Конструктор:
var calendar = new Calendar({
year: 2012, // календарь для года 2012
month: 2 // месяц - март (нумерация с нуля!)
});
События:
selected— при изменении даты.
Методы:
setValue(date, disableEvent)— устанавливает датуdate. Если второй аргументtrue, то событие не генерируется.getElement()— возвращает DOM-элемент для компоненты для вставки в документ. При необходимости (первый пока) создаёт DOM.
Использование - добавление в документ:
var container = document.getElementById('widget')
container.appendChild(calendar.getElement());
Использование - подписка на изменение и вывод значения:
calendar.on("select", function() {
document.getElementById('value').innerHTML = this.getValue();
})
Пример в действии:
Исходный документ: tutorial/browser/events/calendar-src/index.html.
Создайте календарь, который показывает два месяца сразу. Стрелки позволяют менять текущий месяц.
Конструктор:
var datePicker = new DatePicker({
template: template, // шаблон (см. исходный файл внизу)
year: 2012, // календарь для года 2012
month: 2 // месяц - март (нумерация с нуля!)
});
События:
selected— при изменении даты.
Методы:
setValue(date, disableEvent)— устанавливает датуdate. Если второй аргументtrue, то событие не генерируется.getElement()— возвращает DOM-элемент для компоненты для вставки в документ. При необходимости (первый пока) создаёт DOM.
Использование - добавление в документ:
document.body.appendChild(datePicker.getElement());
Использование - подписка на изменение и вывод значения:
datePicker.on("select", function(value) {
document.getElementById("value").innerHTML = value;
});
Пример в действии:
Исходный документ: tutorial/browser/events/datepicker-src/index.html.
В решении используйте готовый компонент — календарь из задачи Календарь.
Решение: tutorial/browser/events/datepicker/index.html.
В нём присутствуют файлы из задачи про календарь, т.к. он используется в качестве компонента.
Комментарии
- Приветствуются комментарии, содержащие дополнения и вопросы по статье, и ответы на них.
- Если ваш комментарий касается задачи -- откройте её в отдельном окне и напишите там.
- Комментарии без смысла, с рекламой или не о статье вообще - удаляются.