Айфонные UI-прелести с помощью jQuery: UITableView

Айфонные UI-прелести с помощью jQuery: UITableView

Всем привет.

Несколько лет назад, как только мне в руки попал айпод, и я увидел, как там организованы списки песен, мне сразу захотелось реализовать такое же с помощью JavaScript.

Но до вчерашнего дня все не находилось времени.

Суть скрипта в том, что он позволяет заголовку списка «зависать» над содержимым, пока мы его перематываем и не дойдем до следующего заголовка.

Чтобы сразу все стало ясно, приведу скриншот списка песен:

Пока мы перематываем песни, начинающиеся на «А», заголовок продолжает висеть над списком. А как только приближается следующий заголовок «B», он плавно смещает предыдущий и становится на его место.

В библиотеке UIKit для разработки iOS-приложений такой элемент интерфейса называется UITableView.

Кстати, такой элемент в интерфейсе, назовем его «подвисающий заголовок», еще есть в Пикасе. Если на айфоне этот подвисающий заголовок не очень-то функционален, то в Пикасе эта функция очень даже оправдана. Во-первых всегда позволяет понять, какую коллекцию сейчас просматриваешь, во-вторых выполнить какие-то функции.

В вебе я примеров пока не встречал, хотя область полезного применения безгранична.

Итак, к делу.

Начнем с разметки. У нас будет список, состоящий из заголовков и их содержимого. Содержимое — это ненумерованный список. 

 <body>
<div id="TableViewWrapper">
<dl id="TableView">
<dt class="header">First Header</dt>
<dd>
<ul>
<li class="item">Item 1</li>
<li class="item">Item 1</li>
<li class="item">Item 1</li>
<li class="item">Item 1</li>
<li class="item">Item 1</li>
<li class="item">Item 1</li>
</ul>
</dd>
<dt class="header">Second Header</dt>
<dd>
<ul>
<li class="item">Item 1</li>
<li class="item">Item 3</li>
<li class="item">Item 1</li>
<li class="item">Item 1</li>
</ul>
</dd>
<dt class="header">Third Header</dt>
<dd>
<ul>
<li class="item">Item 3</li>
<li class="item">Item 2</li>
<li class="item">Item 3</li>
<li class="item">Item 2</li>
<li class="item">Item 3</li>
<li class="item">Item 2</li>
<li class="item">Item 1</li>
<li class="item">Item 1</li>
<li class="item">Item 3</li>
<li class="item">Item 2</li>
<li class="item">Item 3</li>
</ul>
</dd>
</dl>
</div>
</body>

Внешний div#TableViewWrapper нам необходим и я позже поясню почему.

CSS:

body
{
font-family: Trebuchet MS;
background: #F7F4EA;
}
body, ul,li, dl, dd,dt {
margin: 0;
padding: 0;
}
li
{
list-style: none;
}

.header
{
line-height:40px;
font-weight: bold;
background: transparent url(bg.png) left top repeat;
padding: 0 10px;
color: #fff;
}
.item
{
border-top: solid 1px #999;
line-height: 80px;
padding: 0 10px;
background: #fefefe;
}
.item:first-child
{
border-top: none;
}
#TableViewWrapper
{
width: 400px;
height: 500px;
margin: 10px auto;
background:#fafafa;
position:relative;/*это важно*/
overflow:hidden;
}
#TableView
{
width: 100%;
height: 100%;
overflow:auto;
}

Ловим событие скролла на элементе #TableView:

$('#TableView').scroll(function () { 
//здесь ловятся события прокрутки колесика мышки, перетаскивания ползунка или перемотки с помощью тач-пада
});

При каждом событии скролла, мы будем пробегаться по всем заголовкам и смотреть их позицию. Для того чтобы узнать позицию, используется функция .position(). Ее следует отличать от функции .offset(), потому что .position() возвращает позицию элемента относительно ближайшего оффсет-парента. Прошу прощения, не могу перевести нормально. Другими словами, относительно элемента, у которого position указан как relative, absolute или fixed.

Именно поэтому нам пришлось обернуть #TableView в #TableViewWrapper и прописать у него position: relative. Для того, чтобы корректно определять позицию заголовка относительно этого элемента.

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

Функция, которая будет выполнять всю тяжелую работу, выглядит достаточно просто: 

 function HandleHeaderPosition(TableView) {

$('.header', TableView).each(function (i) {

var headerWidth = $(this).width();
var headerHeight = $(this).height();
var currentHead = $(this);
var currentContent = $(this).next();
var dif = currentContent.position().top + currentContent.height();
// если заголовок поднялся выше крыши, открепить его
if (currentHead.position().top <= 0) {
var top = dif > 0 ? 0 : dif;
currentHead.css({ position: 'absolute', top: top, width: headerWidth });
currentContent.css({ marginTop: currentHead.height() });
}
// если заголовок сдвинулся ниже высоты, от вкрепить назад в список
if (currentContent.position().top >= headerHeight
//или контент спустился ниже заголовка, а заголовок откреплен
|| (currentHead.position().top <= 0 && currentContent.position().top > 0)) {
currentHead.css({ position: 'inherit', width: 'auto' });
currentContent.css({ marginTop: 0 });
}
});
}

Вот, собственно, и все.

Для того, чтобы перемотка была плавной во всех браузерах (в опере перемотка плавная по-умолчанию), в демо я добавил плагин jquery.mousewheel (об этом плагине я упоминал в другой статье об айфонных ui-прелестях). От него можно избавиться — это не повлияет на функциональность.

Демо Архив

Расскажите друзьям

Оцените статью:
  • 1
  • 2
  • 3
  • 4
  • 5

Комментарии — 18

faster
прикольная штука, но пока не могу сообразить где ее можно применить)
#
faster
legendar
>В вебе я примеров пока не встречал, хотя область полезного применения безгранична.
http://www.google.com.ua/images?hl=ru&q=flowers
http://nodejs.org/docs/v0.2.6/api.html
Александр
А можно ведь сделать и так, чтобы заголовки списков "накапливались" друг под другом, показывая путь к текущему просматриваемому списку. Для древовидных форумов было бы, пожалуй, весьма полезно...
#
Александр
Вано
Аналогично, не могу сообразить. Вообще веб мало работает с алфавитными списками. Это либо, действительно, музыка, либо фотки, либо очень большой каталог товаров где 1000+ категорий (включая вложенные). Но для "общего развития" - интересно. Если не секрет, Жека, писал для применения где-то, или из спортивного интереса?
#
Вано
sergy
подтормаживает немного, но идея свежа. спасибо
#
sergy
sasha
Да вы что ребят? Это не свежая идея (не хочется говорить «боян»). Допустим, на том же jqueryfordesiners.com видео-таториал с подобными «подвисающими заголовками» уже очень давно висит, там правда заголовки друг друга не толкают, а наплывают друг на друга.
#
sasha
sasha
извняюсь, случайно отправил сообщение без пруфлинка: http://jqueryfordesigners.com/iphone-like-sliding-headers/
#
sasha
senik11
Евгений, вы случаем не из Беларуси?=)
#
senik11
Дмитрий
Спасибо, найдет применение в списке френдов, соцсети...
Сергей
>>При каждом событии скролла, мы будем пробегаться по всем заголовкам и смотреть их позицию
Проще было бы запомнить все позиции заголовков, чем каждый раз высчитывать по новой. Тогда бы быстрее работало.
#
Сергей
Zhendalf
legendar, sasha
Предложенный мной вариант списка не похож на то, что вы показали.
senik11,
Сейчас мое тело находится в Минске, да.
Вано,
Буду применять. В будущих статьях будет раскрыто, где.
sergy, Сергей
Тут есть много что можно улучшить и исправить. Очень плохо работает с тач-падом, особенно на маке. Будем называть то, что есть прототипом.
legendar
>Предложенный мной вариант списка не похож на то, что вы показали.
Чем он не похож? о_О
Zhendalf
>Чем он не похож?
Если бы на айфоне или в пикасе были такие заголовки как на Node.js, то это было бы не привлекательной фишкой интерфейса, а его уродством.

А я хотел именно эту привлекательность приподнести: плавное толкание одного заголовка другим, а не обыкновенную смену.
legendar
мда.. стилизация и эффект смены заголовка это не главное.

"Суть скрипта в том, что он позволяет заголовку списка «зависать» над содержимым, пока мы его перематываем и не дойдем до следующего заголовка."
Ваши ведь слова? =) А все что идет дальше это уже нюансы конкретного частного примера.
А я всего лишь ответил на вашу фразу:
"В вебе я примеров пока не встречал"
имея ввиду идею, а не способ реализации.
Амаль
> прикольная штука, но пока не могу сообразить где ее можно применить)

Вот пример реального применения такого эффекта:
http://docs.pravo.ru/document/view/59178

Очень удобная вещь.
Zhendalf
Спасибо, Амаль! Замечательный пример.
sasha
ой, Zhendalf, извните я просто думал, что суть вашего скрипта в том, что он позволяет заголовку списка «зависать» над содержимым, пока мы его перематываем и не дойдем до следующего заголовка. А если суть в том чтобы толкать заголовок следующим заголовком, то да, согласен, не похож. Но я же об этом написал в первом коменте.
Впрочем, всё равно извините за смуту. Прикольно придумали ;-) Спасибо.
#
sasha
Матвей
Спасибо за "Волшебный" функционал!!!
У меня вопрос, как такую прокрутку сделать на чистом Javascript?
Основное внимание при этом обращая на вес кода в килобайтах.
Я можно сказать ещё новичок, но синтаксис javascript понимаю хорошо,
буду благодарен (кликая рекламу!)) за краткое руководство по написанию данного скрипта
(что в него должно входить, с чего начать и т.д.)
#
Матвей

Новый комментарий

как выглядит какой тег
жирный текст <b>жирный текст</b>
курсивный тект <i>курсивный тект</i>
зачеркнутый текст <s>зачеркнутый текст</s>
подчеркнутый текст <u>подчеркнутый текст</u>
ссылка <a href="адрес">ссылка</a>
function foo() { ... }
<pre><code>function foo() { ... } </code></pre>
разрешенные теги или посмотреть как будет выглядеть
как выглядит какой тег
жирный текст <b>жирный текст</b>
курсивный тект <i>курсивный тект</i>
зачеркнутый текст <s>зачеркнутый текст</s>
подчеркнутый текст <u>подчеркнутый текст</u>
ссылка <a href="адрес">ссылка</a>
function foo() { ... }
<pre><code>function foo() { ... } </code></pre>
разрешенные теги или посмотреть как будет выглядеть

metin2 pvp metin2 pvp serverler pvp serverler