Заметки с тегом «utility» (страница 3)

Обнаружение браузера Opera 9

Совсем недавно я начал применять web-шрифты в реальных проектах. Благодаря простым утилитам (например, @font-face Generator ) можно быстро сконвертировать шрифт в нужные форматы. Но статья совсем не о том. Эти web-шрифты прекрасно (если не брать во внимание проблемы со сглаживанием) отображаются во всех браузерах (даже в IE6!!!) кроме Opera версии 9 и ниже. В связи с эти фактом я и озадачился методами обнаружения Opera 9.

Очевидным способом является проверка строчки User-Agent . Этот вариант решения проблемы хорош до тех пор, пока не сталкиваешься с подменой строки.

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

Так для браузеров Opera до 10 версии будет характерным отличительным признаком отсутствие события contextmenu у DOM-элемента.


function () {
  var isPresent = null;
  if (document.createElement) {
    var el = document.createElement("p");
    if (el && el.setAttribute) {
      el.setAttribute("oncontextmenu", "");
      isPresent = typeof el.oncontextmenu != "undefined";
    }
  }
  return isPresent;
}

Обнаружение такой не очевидной особенности гораздо эффективнее, например, по сравнению с обнаружением поддержки web-шрифтов потому, что шрифту придется загрузить по сети.

Так же, наверное, можно использовать какие-то CSS-хаки, специфичные для Opera, по аналогии с тем, как я делал определение IE6.

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

Событие окончания загрузки картинки

Чтобы отследить момент загрузки картинки одного обработчика события «load » бывает не достаточно. Если он будет прикреплен к картинке после того, как она уже была загружена, то событие больше не повторится и обработчик не будет вызван.

Поможет решить эту проблему вспомогательный плагин к jQuery.


;(function ($) {
    $.fn.bindImageLoad = function (callback) {
        function isImageLoaded(img) {
            // Во время события load IE и другие браузеры правильно
            // определяют состояние картинки через атрибут complete.
            // Исключение составляют Gecko-based браузеры.
            if (!img.complete) {
                return false;
            }
            // Тем не менее, у них есть два очень полезных свойства: naturalWidth и naturalHeight.
            // Они дают истинный размер изображения. Если какртинка еще не загрузилась,
            // то они должны быть равны нулю.
            if (typeof img.naturalWidth !== "undefined" && img.naturalWidth === 0) {
                return false;
            }
            // Картинка загружена.
            return true;
        }

        return this.each(function () {
            var ele = $(this);
            if (ele.is("img") && $.isFunction(callback)) {
                ele.one("load", callback);
                if (isImageLoaded(this)) {
                    ele.trigger("load");
                }
            }
        });
    };
})(jQuery);

Обновление: Работу плагина можно посмотреть в примере. Там используется немного модифицированная версия, которая формирует специальное событие.

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

Часы со стрелками без применения 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);
})();

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

Рассчитать ширину скролбара

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

  1. добавить два div в body и разместить их за пределами экрана;
  2. определить ширину внутреннего div;
  3. установить значение «scroll» свойства overflow у внешнего div;
  4. опять определить ширину внутреннего div;
  5. удалить оба div;
  6. вернуть разницу между измеренными ранее значениями ширины.

Для простоты манипуляции с документом используем jQuery


function scrollbarWidth() {
    var div = $('<div style="width:50px; height:50px; overflow:hidden; position:absolute; top:-200px; left:-200px;"><div style="height:100px;"></div></div>').appendTo('body');
    var w1 = $('div', div).innerWidth();
    div.css('overflow-y', 'scroll');
    var w2 = $('div', div).innerWidth();
    $(div).remove();
    return (w1 - w2);
}

Источник: http://jdsharp.us/jQuery/minute/calculate-scrollbar-width.php

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

Система оповещения модулей

Приятно наблюдать за тем как развиваются языки программирования, за появлением различных фреймворков и паттернов. Так в моем любимом JavaScript в последнее время появилась хорошая тенденция оформлять код в виде модулей (классов). Это обеспечивает и лучшую читаемость программ и повышает повторное использование кода.

Разделение функционала на отдельные модули требует от программиста обеспечить их коммуникацию между собой. Одним из способов обеспечения такой связи служит система оповещения модулей, заинтересованных в получении информации о каких-то событиях, происходящих в других модулях.


/**
 *  Observable Object
 *  Just call it in constructor of your class (delegate pattern)
 *  Ex: var me = Observable(); // create new object
 *      or
 *      Observable(me);        // extend existing object
 */
var Observable = function (target) {
    // callback holder
    var observers = {};
    // create new object if needed
    target = target || {};

    target.attachObserver = function (eventType, callback) {
        if (!observers[eventType]) {
            observers[eventType] = [];
        }
        observers[eventType].push(callback);
        return this;
    };

    target.detachObserver = function (eventType, callback) {
        var a, i;
        if (eventType) {
            if (observers[eventType] && callback) {
                a = observers[eventType];
                for (i = 0; i < a.length; i++) {
                    if (a[i] === callback) {
                        a.splice(i, 1);
                        break;
                    }
                }
            } else {
                delete observers[eventType];
            }
        } else {
            observers = {};
        }
        return this;
    };

    target.notify = function () {
        var a, i, handler, data = [];
        // copy notification arguments
        Array.prototype.push.apply(data, arguments);
        a = observers[data.shift()];
        if (a && a.length > 0) {
            for (i = 0; i < a.length; i++) {
                handler = a[i];
                if (typeof handler === 'string') {
                    handler = this[handler];
                }
                if (handler instanceof Function) {
                    handler.apply(this, data);
                }
            }
        }
        return this;
    };

    return target;
};

Этот компонент при инициализации создает в указанном объекте 3 метода:

  • attachObserver — добавляет колбек для определенного события
  • detachObserver — удаляет один или все колбеки для определенного события
  • notify — инициализирует событие, при этом вызываются все зарегистрированные колбеки с указанными аргументами

Все указанные методы поддерживают цепочный вызов.


var FieldObject = function (root) {
    var me = Observable(this);

    $(root).append('<div class="field"><input type="text" value="" /></div>');
    $('div.field input', root).change(function () {
        me.notify('valueDidChange', this.value);
    });

    return me;
};

FieldObject($('body'))
    .attachObserver('valueDidChange', function (data) {
        alert(data);
    });

В этом примере я создал простой программный компонент FieldObject, которой добавляет на страницу текстовое поле, а затем подписался на событие valueDidChange , которое случается при обновлении этого текстового поля. Весь фокус в том, что я могу не знать, как устроен этот объект, и откуда получить значение его текстового поля. Все что мне нужно – это отреагировать на изменение поля.

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