Заметки за апрель 2009 года

Часы со стрелками без применения Flash

Для сайта компании «Международный Центр Финансовых Операций» заказчик попросил сделать часы со стрелками, показывающие время в Москве, Нью-Йорке и Токио. Первое, что могло бы прийти в голову – «Flash». А не тут-то было… Часики очень быстро можно сделать и на HTML+CSS+JavaScript.

Самое сложное и долгое это, пожалуй, спрайты стрелок.

Дизайнер приготовил для меня контуры стрелок в Photoshop, а я с помощь экшина создал 24 слоя, на которых стрелки повернуты на 15° относительно предыдущего слоя. Признаюсь, что эти значения были выбраны эмпирически исключительно исходя из здравого смысла. Часы на странице носят скорее декоративный характер, нежели практически и поэтому сомнительно, что кто-то будет за ними специально следить. Технически, можно было бы сделать и все 60 фаз.

Спрайты стрелок часов

Затем я расположил слои один над другим, чтобы изменять изображение исключительно с помощью смещения положения фона у блока.

В стрелки часов я отделил от фона с помощью техники, аналогичной той, что описывал Сергей Чикуенок в статье «Как вырезать картинку из фона». Использовать PNG-24 с прозрачностью было не приемлемо из-за отсутствия поддержки в IE6, а отрисовка таких мелких деталей без антиалиасинга выглядела просто ужасно.

Итак, подготовительные работы закончены. Приступаем к созданию механизма часов.

<div id="clocks">
    <div class="city moscow">
        <div class="hour"></div>
        <div class="minute"></div>
    </div>
    <div class="city newyork">
        <div class="hour"></div>
        <div class="minute"></div>
    </div>
    <div class="city tokyo">
        <div class="hour"></div>
        <div class="minute"></div>
    </div>
</div>

#clocks {
    background: url(clocks-template.png) no-repeat left top;
    width: 150px;
    height: 77px;
    position: relative;
}
#clocks .city,
#clocks .hour,
#clocks .minute {
    width: 45px;
    height: 45px;
    position: absolute;
}
#clocks .city {
    overflow: hidden;
    top: 14px;
}
#clocks .hour {
    background: url(clock-hour-sprite.gif) no-repeat 0 0;
}
#clocks .minute {
    background: url(clock-minute-sprite.gif) no-repeat 0 0;
}
#clocks .moscow {
    left: 0;
}
#clocks .newyork {
    left: 53px;
}
#clocks .tokyo {
    left: 105px;
}

(function () {
    var dateObj = new Date();
    var m = dateObj.getMinutes(), h = dateObj.getHours() + (dateObj.getTimezoneOffset() + m) / 60;

Переменные m и h содержат минуты и часы соответственно. Более того, значение часов приведено к GMT и скорректировано в зависимости от количества минут.

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

    var minute = Math.floor((m + 1.25) / 2.5) % 24;

    function getHourForCity(h, offset) {
        return Math.floor((12 + h + offset + 0.25) / 0.5) % 24;
    }

Волшебная функция, которая позволит нам определить используется ли у пользователя «летнее» время в системе или нет.

    function daylightSaving() {
        var now = new Date(),
            nowTZ = now.getTimezoneOffset(),
            winterTZ = new Date(now.getFullYear(), 1, 1).getTimezoneOffset(),
            summerTZ = new Date(now.getFullYear(), 7, 1).getTimezoneOffset();
        return (winterTZ !== summerTZ) && (nowTZ === summerTZ) ? 1 : 0;
    }

    var recovery = daylightSaving();

Устанавливаем позицию фона так, чтобы отобразилась нужная нам стрелка.

    $('#clocks .minute').css('backgroundPosition', 'left -' + 45 * minute + 'px');
    $('#clocks .mosсow .hour').css('backgroundPosition', 'left -' + 45 * getHourForCity(h, 3 + recovery) + 'px');
    $('#clocks .newyork .hour').css('backgroundPosition', 'left -' + 45 * getHourForCity(h, -5 + recovery) + 'px');

В Японии не переводят стрелки на «летнее» время, по этому коррекция часового пояса не требуется.

    $('#clocks .tokyo .hour').css('backgroundPosition', 'left -' + 45 * getHourForCity(h, 9) + 'px');

Обновляем положение стрелок каждую минуту. Мне очень нравится этот шаблон – замыкание + setTimeout, потому что он позволяет выполнить функцию сразу, чего не может обеспечить setInterval.

    setTimeout(arguments.callee, 60000);
})();
Оставте свой комментарий

Запомнить порядок сортировки jQuery UI Sortable

В сервисе Google «Вопросы и ответы» одним пользователем был задан этот вопрос и, к моему удивлению, очень долго никто не давал на него ответа. А на самом деле эта задача очень просто решается благодаря встроенному методу serialize.

Сейчас я хочу разобрать только основную функцию программы:

Сохраняем ссылку на корневой элемент сортируемого списка. Он не однократно потребуется нам в дальнейшем. Хоть эта функция и работает очень быстро, но так получить доступ можно будет еще быстрее.

var root = $('#items');

Назначаем всем элементам списка уникальные идентификаторы. Это обязательное условие правильной работы метода serialize. Префикс может быть произвольным, но в наших интересах, чтобы он был как можно короче. В качестве разделителя можно использовать символы подчеркивание, равенство или дефис.

$('> *', root).each(function (index) {
    this.id = 'item-' + index;
});

Инициализируем виджет Sortable и перехватываем событие update, чтобы сохранять состояние списка.

root.sortable({
    'update': function (event, ui) {
        var order = $(this).sortable('serialize');
        $.cookies.set('sortable', order);
    }
});

Восстанавливаем порядок элементов списка после загрузки страницы.

var c = $.cookies.get('sortable');
if (c) {
    $.each(c.split('&'), function () {
        var id = this.replace('[]=', '-');
        $('#' + id).appendTo(root);
    });
}

Пробегаемся по всем элементам, сохраненным в cookie, и перемещаем их в конец списка. Если был сохранен порядок для всех элементов, то они естественным образом займут свои места. Если для каких-то элементов позиция не сохранялась, то они «всплывут» наверх списка.

Отдельно хочу заметить, что в этом примере вместо штатного API для доступа к cookies использовался плагин, который значительно облегчил работу с ними.

Комментарии к заметке: 9

Презентация картинок в виде слайдшоу

Как сделать красивое оформление для презентации я писал в заметке о Lightbox. А теперь расскажу, как собственно можно сделать саму презентацию.

Поставщиком картинок у нас будет сервис Flickr.

/**
 * Flickr photos provider
 */
(function () {

    var FLICKR_KEY = '4e1a2666b38001e7cb295852430274fe';
    var FLICKR_PREFIX = 'flickr-photo-';

Создаем замыкание, ограничивающее область видимости переменных и объявляем некоторые константы.

Замечу, что FLICKR_KEY содержит уникальный ключ Flickr API, который каждый разработчик должен бесплатно получить для своего приложения.

    APP.Flickr = function () {
        var me = Observable(this);
        me.init();
    };

Конструктор нашего поставщика. Он так же заботится об инициализации сервиса в целом. Для связи сервиса с приложением используется система событий.

    APP.Flickr.prototype = {
        init: function () {
            this.imageCache = document.createElement('div');
            $(this.imageCache)
                .attr('id', FLICKR_PREFIX + 'holder')
                .css({
                    'position': 'absolute',
                    'width': 0,
                    'height': 0,
                    'overflow': 'hidden'
                })
                .appendTo('body');
        },

Все изображения кэшируются для дальнейшего использования в невидимом блоке. Этот прием оказывается очень эффективным с небольшим количеством картинок в галерее.

        load: function (tags) {
            var me = this;
            $.getJSON('http://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=' + FLICKR_KEY + '&format=json&tags=' + tags + '&content_type=1&jsoncallback=?',
                function(data) {
                    me.photos = data.photos;
                    me.notify('photosDidLoad', me.photos);
                }
            );
        },

Загружаем список картинок с заданными критериями поиска. Для взаимодействия с Flickr используется команда flickr.photos.search и протокол обмена информацией JSON.

Метод генерирует событие photosDidLoad, после удачного получения списка.

        showPhoto: function (index) {
            var me = this;
            var photo = me.photos.photo[index], img;
            var cached = $('#' + FLICKR_PREFIX + photo.id);
            if (cached.length === 0) {
                img = $('<img id="' + FLICKR_PREFIX + photo.id + '" title="' + photo.title + '" class="photo" />')
                        .appendTo(this.imageCache)
                        .load(function () {
                            me.notify('imageReady', img);
                        })
                        .attr('src', 'http://farm' + photo.farm + '.static.flickr.com/' + photo.server + '/' + photo.id + '_' + photo.secret + '.jpg');
            } else {
                me.notify('imageReady', cached);
            }
        }
    };
})();

Загружаем изображение из списка или берем его из кеша, если оно было уже загружено ранее.

Метод генерирует событие imageReady и передает подписчикам изображение, когда оно будет готово к использованию.

Теперь перейдем к коду нашего приложения, использующее только что созданный сервис и эффектно сменяющее картинки. Создаем объект поставщика картинок и подписываемся на его события.

$(function () {
    var provider = new APP.Flickr(),
        wait = false;
    provider.attachObserver('photosDidLoad', function (data) {
        var i = 0;
        (function () {
            i = (i >= data.length) ? 0 : i;
            if (!wait) {
                wait = true;
                provider.showPhoto(i++);
            }
            setTimeout(arguments.callee, 2000);
        })();
    });

Когда поставщик сообщит о том, что список загружен, мы запускаем циклический просмотр изображений с интервалом в 2 секунды.

Так как в JavaScript нет паузы как таковой, то применяется такой отложенный асинхронный вызов анонимной функции. Это стало возможным благодаря тому, что внутри любой функции поле callee объекта arguments всегда ссылается на выполняющуюся функцию.

    provider.attachObserver('imageReady', function (img) {
        $('#showroom img.photo')
                .animate({
                    'opacity': 0
                }, function () {
                    $(provider.imageCache).append(this);
                });
        $(img).appendTo('#showroom')
                .css({
                    'opacity': 0
                })
                .centering()
                .animate({
                    'opacity': 1
                }, function () {
                    wait = false;
                });
    });

Как только очередная картинка будет загружена, она центрируется на странице и ее прозрачность плавно меняется с 0 до 1. Тем временем, у текущей картинки меняется прозрачность до 0 и затем она отправляется в кеш.

    provider.load('apple,mac');
});

Загружаем список картинок с тегами «apple» и «mac».

Слайдшоу начнется сразу после загрузки страницы и будет продолжаться до тех пор, пока страница не будет закрыта.

Оставте свой комментарий