Pull to refresh

cssHooks — расширяем множество CSS свойств

Reading time5 min
Views3.2K
В этой статье я расскажу об объекте jQuery.cssHooks, который по умолчанию содержится в jQuery. Расширение этого объекта позволяет добавлять новые свойства или значения, прописываемые в методе .css(), которые изначально не поддерживаются теми или иными браузерами. Возможно, для многих наличие CSS хуков не является новостью, но для меня это стало небольшим открытием.
Для тех, кому лень дальше читать, привожу основную мысль. Допустим, вы хотите в jQuery добавить CSS свойство chuck-norris:
$.cssHooks.chuckNorris = {
	get: function(elem) {
		//проводим манипуляции с узлом elem, получаем value
		return value;
	},
	set: function(elem, value) {
		//проводим манипуляции с узлом elem, устанавливаем value
	}
}

$(el).css(‘chuck-norris’, Infinity);
//или $(el).css({‘chuck-norris’: Infinity});
alert($(el).css(‘chuck-norris’)); //Infinity


Далее будет подробно описана «модификация» свойства background-color для поддержки rgba в старых версиях IE и добавление нового, несуществующего в спецификации свойства background-alpha для удобной установки прозрачности фонового цвета. В IE прозрачность цвета будет реализована с помощью использования свойства filter, добавляя элементу градиент, состоящий из двух одинаковых цветов.

Этот способ имеет два недостатка.

Во-первых градиенты не работают для блоков с шириной auto, для этого придется применять хак:
$('div').width('100%');

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


Для начала нам понадобится функция (parseColor), которая будет парсить цвет в разных форматах и возвращать объект {r,g,b,a}. Учтены следующие варианты:
  • Когда на вход поступила пустая строка или transpatent
  • Строка формата #aarrggbb (для градиентов в IE)
  • Строка формата #rrggbb
  • Строка формата #rgb
  • Строка формата rgb[a](r, g, b [, a])

Код этого парсера (который будет не к месту из-за размера) и, собственно, идея частично позаимстсоваы отсюда.

Вторым этапом создадим класс Color, принимающий на вход html узел и содержащий ряд функций, которые в дальнейшем понадобятся.
var Color = function(el){
    
    //функция возвращает объект из трех элементов:
    //является ли браузер IE8-, есть ли в стилях элемента градиент, включен ли этот градиент
    var ieDetect = (function(){
        var ua = navigator.userAgent;
        var result = {}
        
        result.isOldIe = ~ua.indexOf('MSIE 6')    || ~ua.indexOf('MSIE 7') || ~ua.indexOf('MSIE 8');
        result.hasGradient = result.isOldIe &&
                ~el.style.filter.toLowerCase().indexOf('gradient');
        result.isGradientEnabled = result.hasGradient &&
                !!el.filters.item("DXImageTransform.Microsoft.gradient").enabled;
            return result;
    })();
    
    //конвертация объекта с десятичными значениями цвета в объект с шестнадцатиричными
    var colorToHex = function (objColor) {
        var hex = {};
        for (var i in objColor) {
            hex[i] = objColor[i];
            if (i==='a') {
                hex.a = Math.round(hex.a*255);
            }
            hex[i] = hex[i].toString(16);
            //добавляем '0', если полученное значение состоит из одного символа
            hex[i] = hex[i].length == 2 ? hex[i] : '0' + hex[i]; 
        }
        
        return hex;
    }
    
    
    //преобразует объект {r,g,b,a} в строку с заданным форматом
    var colorToString = function(objColor, format) {
        var hexColor = colorToHex(objColor);
        
        switch(format) {
            case 'rgb':     return 'rgb(' + objColor.r + ',' + objColor.g + ',' + objColor.b + ')';
            case 'rgba': return 'rgba(' + objColor.r + ',' + objColor.g + ',' +
                        objColor.b +',' + objColor.a + ')';
            case '#6':     return '#' + hexColor.r + hexColor.g + hexColor.b;
            case '#8':     return '#' + hexColor.a + hexColor.r + hexColor.g + hexColor.b;
        }
    }
    
    //конвертирует один формат цвета в другой
    var convertColor = function(color, format) {
        var colorObj = parseColor(color); 
        return colorToString(colorObj, format)
    }

    //добавляет поддержку getComputedStyle для старых браузеров
    //ниже будет видно, зачем
    if (!window.getComputedStyle) {
        window.getComputedStyle = function(el, pseudo) {
            this.el = el;
            this.getPropertyValue = function(prop) {
                var re = /(\-([a-z]){1})/g;
                if (re.test(prop)) {
                    prop = prop.replace(re, function () {
                        return arguments[2].toUpperCase();
                    });
                }
                return el.currentStyle[prop] ? el.currentStyle[prop] : null;
            }
            return this;
        }
    }
...


Теперь перейдем к самой установке значения цвета. Надеюсь, читателю не составит большого труда понять код, несмотря на его вложенность.
this.setBackgroundColor = function(color) {
    var    newColor,
        newColorObj = parseColor(color);
    if(ieDetect.isOldIe) { // Если IE
        if(newColorObj.a < 1) { // Если альфа меньше единицы
            el.style.backgroundColor = 'transparent';
            newColor = colorToString(newColorObj, '#8');
            if(ieDetect.hasGradient) { // Если есть фильтр градиент
                el.filters.item("DXImageTransform.Microsoft.gradient").enabled = true;
                el.filters.item("DXImageTransform.Microsoft.gradient").startColorstr = newColor;
                el.filters.item("DXImageTransform.Microsoft.gradient").endColorstr = newColor;
            } else { // Если нету фильтра градиент
                el.style.filter +=
                       "progid:DXImageTransform.Microsoft.gradient(enabled='true', startColorstr=" +
                       newColor + ", endColorstr=" + newColor + ")";
            }
        } else { // Если альфа равна единице
            newColor = colorToString(newColorObj, '#6');
            if(ieDetect.hasGradient) { // Если есть фильтр градиент
                el.filters.item("DXImageTransform.Microsoft.gradient").enabled = false;
                el.style.backgroundColor = newColor;
            } else { // Если нету фильтра градиент
                el.style.backgroundColor = newColor;
            }
        }
    } else { // Если не IE
        if(newColorObj.a < 1) { // Если альфа меньше единицы
            newColor = colorToString(newColorObj, 'rgba');
            el.style.backgroundColor = newColor;
        } else { // Если альфа равна единице
            newColor = colorToString(newColorObj, '#6');
            el.style.backgroundColor = newColor;
        }
    }
}


Следующая часть кода отвечает за получение значения цвета. Здесь уже намного проще.
this.getBackgroundColor = function() {
    var color;
    if(ieDetect.isGradientEnabled) { // Если IE и включен градиент
        color = el.filters.item("DXImageTransform.Microsoft.gradient").startColorstr;
        return convertColor(color, 'rgba');
    } else {
        //хак взят отсюда: http://snipplr.com/view/13523/getcomputedstyle-for-ie/
        //вместо этого мы могли бы использовать $(el).css('background-color');
        //но это свойство мы переназначаем, и возникает бесконечная рекурсия
        color = el.style.BackgroundColor ||
                 window.getComputedStyle(el,null).getPropertyValue('background-color');
        return color;
    }
}


Теперь основная часть. Добавляем хук backgroundColor.
$.cssHooks.backgroundColor = {
    get: function(elem) {
        var color = new Color(elem);
        return color.getBackgroundColor();
    },
    set: function( elem, value ) {
        var color = new Color(elem);
        color.setBackgroundColor(value);
    }
}


Добавляем хук backgroundAlpha
$.cssHooks.backgroundAlpha = {
    get: function(elem) {
        var color = new Color(elem);
        var colorStr = color.getBackgroundColor();
        var colorObj = parseColor(colorStr);
        return colorObj.a;
    },
    set: function(elem, value) {
        var color = new Color(elem);
        var colorStr = color.getBackgroundColor();
        var colorObj = parseColor(colorStr);

        //если значением стиля является число, то jQuery приписывает 'px'; фиксим
        colorObj.a = String(value).replace('px', ''); 
        color.setBackgroundColor('rgba('+
                colorObj.r+','+colorObj.g+','+colorObj.b+','+colorObj.a+')');
    }
}


Всё сделано, теперь можно использовать.
p{background-color: #991111; ...}
div{background: url(...) ...}

<div>
	<p>...</p>
	<p>...</p>
</div>

$('p').width('100%');
$('p:eq(0)').css({'background-color':'rgba(0,111,221,0.9)'});
$('p:eq(1)').css('background-alpha', 0.5);


Как мы видим, для объектов с названиями в стиле «camel case» jQuery автоматически добавляет поддержку «дефисного» стиля.

Вывод

Если вы хотите добавить поддержку некоторого свойства, которое не поддерживается (или частично поддерживается) теми или иными браузерами, то стоит забыть о том, что это можно сделать созданием плагина и, вместо этого, использовать $.cssHooks.

Ссылки:
Итоговый вариант: finom.ho.ua/bgalpha
Код хуков из статьи: finom.ho.ua/bgalpha/background-rgba.csshook.jquery.js
cssHooks в альтернативной документации: jqapi.com/#p=jQuery.cssHooks
Tags:
Hubs:
+42
Comments19

Articles