Мастер-классы по Javascript Екатеринбург Ростов-на-Дону Москва Узнать больше...
Содержание (скрыть) Содержание (показать)

Практика, практика, практика!

Создайте всплывающую подсказку над элементом.

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

Вы можете посмотреть поведение подсказки в ифрейме ниже, наведя курсор на ссылку.

Открыть в новом окне

Способ добавления подсказки к элементу:

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-подход более универсален и не зависит от вёрстки.

Решение, шаг 1
Решение
Решение, шаг 1

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

  1. Наведение на элемент mouseover
  2. Уход с элемента mouseout

Скелет кода:

function Tooltip(elem, text) {

  var offsetFromElement = 10; // отступ подсказки от элемента 10px
  var tooltipElem; // тут будет DOM-элемент для подсказки

  elem.onmouseout = function() {
    hide(); // спрятать подсказку
  };

  elem.onmouseover = function() {
    show(); // показать подсказку
  };
}

Решение, шаг 2
Решение, шаг 2

Функция 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.

Решение, шаг 1
Решение
Решение, шаг 1

Три события имеют отношение к подсказке:

  1. При mouseover — показать.
  2. При mousemove — показать на новом месте.
  3. При mouseout — спрятать.

…Но при заходе на элемент происходят сразу оба события: mouseover и mousemove на нём же. Зачем вызывать код для показа два раза? Можно оставить его только в mousemove.

Стиль элемента подсказки:

.tooltip {
  position:absolute;
  left: -9999px; /* изначально спрятана за пределы экрана */
  z-index:100; /* подсказка должна перекрывать другие элементы */
  ...
}

Решение, шаг 2
Решение, шаг 2

Скелет кода:

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;  // (**)
}

Здесь есть еще две тонкости.

  1. Для того, чтобы можно было получить offsetHeight/offsetWidth, подсказка должна быть скрыта при помощи visibility:hidden или left/top за пределами экрана, но не display:none.
  2. В строках (*) и (**) подсказку сдвигаем немного левее/выше курсора (на 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 с вспомогательными функциями.

Решение, шаг 1
Решение
Решение, шаг 1

Для захвата проще всего создать дополнительные 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, «восток» по-английски).

Использование сторон света для обозначения направления можно часто встретить в скриптах.

Решение, шаг 2
Решение, шаг 2

Алгоритм:

  1. При инициализации IMG оборачивается во внешнюю обертку и обкладывается DIV'ами, на которых ловим mousedown. Обертка нужна, чтобы абсолютно позиционировать DIV'ы внутри неё под/сбоку изображения.
  2. При наступлении mousedown, начинаем ловить document.mousemove и подгонять картинку под размер, а обертку — под картинку.

    Желательно заодно отменить браузерное выделение и Drag’n’Drop, возвратив false из собработчиков событий mousedown и dragstart.

  3. При наступлении mouseup — конец.
Решение, шаг 3
Решение, шаг 3

Более подробно детали отмечены в комментариях к решению: 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 с вспомогательными функциями.

HTML/CSS
Решение
HTML/CSS

Область выделения — это 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.

В нём присутствуют файлы из задачи про календарь, т.к. он используется в качестве компонента.


Комментарии

  1. Приветствуются комментарии, содержащие дополнения и вопросы по статье, и ответы на них.
  2. Если ваш комментарий касается задачи -- откройте её в отдельном окне и напишите там.
  3. Комментарии без смысла, с рекламой или не о статье вообще - удаляются.
Наверх

Содержание

Реклама

Нашли опечатку?

Нашли опечатку на сайте? Что-то кажется странным?
Выделите соответствующий текст и нажмите Ctrl+Enter!

Последние Комментарии

Помоги другим!

Помоги другим узнать о хорошей статье!