Айфонные 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>
разрешенные теги или посмотреть как будет выглядеть
counter strike mt2 silkroad pvp metin2 metin2 pvp knight pvp gm olarak başlayan pvpler pvp silkroad pvp serverler counter strike serverler msn show cam show görüntülü sohbet oyunlar suskunlar gazete oku