CSS3. Работа с множественными фонами

  • Tutorial
Мы уже ранее затрагивали возможности модуля CSS3 Backgrounds and Borders, рассматривая работу с тенями (box-shadow). Сегодня мы немного поговорим о еще одной интересной возможности — использовании нескольких изображений в фоне.

Композиция фонов



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

Классический подход


Итак, нам нужно разместить несколько фоновых изображений одно над другим. Как обычно решается эта задача? Очень просто: на каждое фоновое изображение создается блок, которому прописывается соответствующее фоновое изображение. Блоки либо вкладываются друг в друга, либо размещаются подряд с соответствующими правилами позиционирования. Вот простой пример:

<div class="sample1">
    <div class="sea">
        <div class="mermaid"><div class="fishing"></div></div>                
        <div class="fish"></div>
    </div>
</div>


Блок с классом «fishing» внутри «mermaid» исключительно для демонстрационных целей.

Теперь немного стилей:
.sample1 .sea, .sample1 .mermaid, .sample1 .fishing {
    height:300px;
    width:480px;
    position: relative;
}
            
.sample1 .sea {
    background: url(media/sea.png) repeat-x top left;          
}
            
.sample1 .mermaid {
    background: url(media/mermaid.svg) repeat-x bottom left;
}
        
.sample1 .fish {
    background: url(media/fish.svg) no-repeat;
    height:70px;
    width:100px;
    left: 30px;
    top: 90px;
    position: absolute;
}
            
.sample1 .fishing {
    background: url(media/fishing.svg) no-repeat top right 10px;
}


Результат:
Sample 1. Classic nested divs

В данном примере три вложенных фона и один блок с рыбками, расположенный рядом с «фоновыми» блоками. В теории, рыбок можно перемещать, например, с помощью JavaScript или CSS3 Transitions/Animations.

Кстати, в этом примере для ".fishing" используется новый синтаксис для позиционирования фона, также определенный в CSS3:
background: url(media/fishing.svg) no-repeat top right 10px;

На текущий момент он поддерживается в IE9+ и Opera 11+, но не поддерживается в Firefox 10 и Chrome 16. Так что пользователи последних двух браузов поймать рыбку пока не смогут.

Поехали дальше. Как упростить эту конструкцию?

Множественные фоны


На помощь приходит новая опция, добавленная в CSS3, — возможность определять сразу несколько фоновых изображений для одного элемента. Выглядит это следующим образом:

<div class="sample2">
    <div class="sea">                     
        <div class="fish"></div>
    </div>
</div>


И соответствующие стили:
.sample2 .sea {
    height:300px;
    width:480px;
    position: relative;
    background-image:  url("media/fishing.svg"), url("media/mermaid.svg"), url("media/sea.png");  
    background-position: top right 10px, bottom left, top left;
    background-repeat: no-repeat, repeat-x, repeat-x ;
}
            
.sample2 .fish {
    background: url("media/fish.svg") no-repeat;
    height:70px;
    width:100px;
    left: 30px;
    top: 90px;
    position: absolute;
}

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

Результат полностью совпадает:
Sample 2. Using multiple backgrounds

Одним правилом


Если рыбок не нужно выделять в отдельный блок для последующих манипуляций, всю картинку можно переписать одним простым правилом:
<div class="sample3">
    <div class="sea"></div>
</div>


Стили:
.sample3 .sea {
    height:300px;
    width:480px;
    position: relative;
    background-image:  url("media/fishing.svg"), url("media/mermaid.svg"), url("media/fish.svg"), url("media/sea.png");  
    background-position: top right 10px, bottom left,  30px 90px, top left;
    background-repeat: no-repeat, repeat-x ;
}


Картинку с результатом приводить не буду — поверьте, она совпадает с двумя картинками выше. А вот на стили обратите внимание еще раз, особенно на «background-repeat» — согласно спецификации, если часть списка в конце пропущена, то браузер должен повторить указанный список нужное число раз, чтобы соответствовать количеству изображений в списке.

В данном случае, это эквивалентно такому описанию:
background-repeat: no-repeat, repeat-x, <mark>no-repeat, repeat-x</mark>;


Еще короче


Если вы помните CSS 2.1, в нем определена возможность описывать фоновые изображения в краткой форме. Как на счет множественных изображений? Это также возможно:

.sample4 .sea {
    height:300px;
    width:480px;
    position: relative;
    background: url("media/fishing.svg") top right 10px no-repeat, 
                url("media/mermaid.svg") bottom left repeat-x,
                url("media/fish.svg") 30px 90px no-repeat,
                url("media/sea.png") repeat-x;                
            }


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

Динамичные изображения


Если композиция статична или динамична не более, чем в зависимости от размеров контейнера, тогда множественные фоны очевидно упрощают конструкцию страницы. А что делать, если с отдельными элементами композиции нужно работать независимо из javascript (перемещать, прокручивать и т.п.)?

Кстати, вот пример из жизни — тема с одуванчиком в Яндексе:
Yandex
Если вы залезете в код, вы увидите там примерно следующее:
<div class=b-skin-bg sizcache="272" sizset="0">
	<div class=b-fluff-bg sizcache="272" sizset="0">
		<div class=b-fluff__sky sizcache="272" sizset="0">
			<div style="background-position: 3244px 0px" class=b-fluff__cloud></div>
			<div style="width: 1200px" class=b-max-width sizcache="214" sizset="0">
				<div class=b-fluff__placeholder sizcache="302" sizset="0">
					<div style="bottom: 105px; display: none; left: 940px" class="b-fluff__item b-fluff_item_3" jQuery1328289994769="30"></div>
					<div style="bottom: 50px; display: none; left: 879px" class="b-fluff__item b-fluff_item_3" jQuery1328289994769="31"></div>
					<div style="bottom: 105px; display: none; left: 940px" class="b-fluff__item b-fluff_item_3" jQuery1328289994769="32"></div>
					...
				</div>
			</div>
		</div>
	</div>
</div>


Блоки с классами «b-fluff-bg», «b-fluff__cloud» и «b-fluff__item» содержат фоновые изображения, накладывающиеся друг на друга. Причем фон с облаками постоянно прокручивается, а одуванчики летают по экрану.

Можно ли это переписать с использованием множественных фонов? В принципе, да, но при условии 1) поддержки этой возможности в целевых браузерах и… 2) читайте дальше ;)


Как добавить динамики множественным фонам? В такой ситуации оказывается удобным, что во внутреннем представлении браузер раскидывает отдельные параметры фоновых изображения по соответствующим правилам. Например, для позиционирования есть «background-position», и для сдвигов достаточно изменять только его. Однако имеется и плата за использование множественных изображений — в этом правиле (и любом аналогичном) необходимо перечислять позицию для всех фонов, заданных для вашего блока, и нельзя сделать это выборочно.

Чтобы добавить нашему фону с рыбками анимации, можно использовать такой код:
$(document).ready(function() {
    var sea = $(".sample5 .sea")[0];
    var fishesX = 30;
    var fishesY = 90;
    var fishX = 0;
    var fishY = 0;
    var mermaidX = 0;
    var t = 0;

    function animationLoop() {
        fishesY = 90 + Math.floor(30 * Math.sin(t++ / 180.0));
        if(--fishesX < 0) fishesX = 480;
        mermaidX += 0.5;
        if(mermaidX > 480) mermaidX = 0;
        fishY = -10 + (10 * Math.cos(t * 0.091));
        fishX = 10 + (5 * Math.sin(t * 0.07));

        sea.style.backgroundPosition = "top " + fishY + "px right " + fishX + "px, " + mermaidX + "px bottom," + fishesX + "px " + fishesY + "px, top left";

        window.requestAnimFrame(animationLoop);
    }
    animationLoop();
});

где
window.requestAnimFrame = (function() {
    return 
        window.requestAnimationFrame || 
        window.msRequestAnimationFrame ||
        window.mozRequestAnimationFrame || 
        window.oRequestAnimationFrame || 
        window.webkitRequestAnimationFrame || 
        (function(callback) { window.setTimeout(callback, 1000 / 60); });
})();


Результат (видео):

Sample 5. Animating

И, кстати, анимации также можно делать через CSS3 Transitions/Animations, но это тема для отдельного обсуждения.

Паралакс и интерактив


Наконец, схожими маневрами можно легко добавить эффекты паралакса или интерактивного взамодействия с фоном:


Множественные фоновые изображения удобны в подобных сценариях, так как пока мы говорим только про фон (а не контент), их использование позволяет избежать замусоривания html-кода и DOM. Но за все приходится платить: я не могу обращаться к отдельным элементам композиции по имени, id, классу или какому либо другому параметру. О порядке элементов в композиции я должен явно помнить в коде и на каждое изменение любого параметра любого элемента фактически я должен склеивать строку, описывающую значения этого параметра для всех элементов, и обновлять ее для всей композиции.

sea.style.backgroundPosition = "top " + fishY + "px right " + fishX + "px, " + mermaidX + "px bottom," + fishesX + "px " + fishesY + "px, top left";


Уверен, что это можно обернуть в удобный код на javascript, который возьмет на себя виртуализацию взаимоотношений с отдельными слоями, оставляя при этом html-код страницы максимально чистым.

Что там с совместимостью?


Все современные версии популярных браузеров, включая IE9+, поддерживают множественные изображения (можно сверяться, например, с сaniuse).


Вы также можете использовать Modernizr, чтобы предоставлять браузерам, не поддерживающим множественные фоны, альтернативные решения. Как написал Chris Coyier в заметке о порядке слоев при использовании множественных фонов, делайте примерно так:

.multiplebgs body {
   /* Awesome multiple BG declarations that transcend reality and imsourcess chicks */
}
.no-multiplebgs body {
  /* laaaaaame fallback */
}

Если вас смущает использование JS для предоставления обратной совместимости, вы можете просто дважды объявить background, правда, это тоже имеет свои минусы в виде возможной двойной загрузки ресурсов (это зависит от реализации обработки css в конкретном браузере):

/* multiple bg fallback */
background: #000 url(...) ...;
/* Awesome multiple BG declarations that transcend reality and imsourcess chicks */
background url(...), url(...), url(...), #000 url(...);



Если вы уже начали думать о Windows 8 имейте в виду, что вы можете использовать множественные фоны при разработке metro style приложений, так как внутри используется тот же движок, что и в IE10.

p.s. В тему: не могу не вспомнить феноменальную статью про принцип цикады.
Microsoft
120.44
Microsoft — мировой лидер в области ПО и ИТ-услуг
Share post

Comments 14

    0
    Были циркадные ритмы, а теперь будут ещё и цикадные.
      +2
      А по существу скажу, что склеивать CSS-строки фонов джаваскриптом — когда нужно подвинуть один из них, не затронув остальные — это такая задача, которая выглядит как-то дико. Потóм ещё и браузеру синтаксически анализировать строку эту склеенную. Что же выигрываем на этом? Быть может, вместо того блоки с индивидуальным фоном, вложенные друг в друга, окажутся и в программировании проще, и на деле быстрее ускоряются браузерами?
        –1
        Скорость нужно тестировать, но определенно вложенные блоки для фона — это семантический мусор.
          +1
          Думаете, что такой подход гораздо лучше для более чистой семантики? Но как быть с браузерами, которые не поддерживают эти вещи? Остаётся только ждать пока у всех современных браузеров это заработает?
            0
            Вы хотите найти ответы на слишком много вопросов сразу :)
            1. На мой вгляд, использование множественных фонов для композиции или простой анимации в фоне сродни использованию border-raduis для скругленных уголков. Еще несколько лет назад (да чего там — и сейчас все еще) для уголков городили огромное количество оберток с вложенными блоками, картинками и другими решениями разной степени извращенности.
            2. Если мы говорим о сложной анимации, когда сама анимация является основным содержимым, то вложенные, соседние и любые другие блоки для отдельных слоев разумны семантически.
            3. Текущая версия multiple backgrounds не является идеальным решением для анимации отдельных слоев (даже более, она спроектирована так, что делать это в сколь-нибудь сложных композициях станосится довольно трудно и, подозреваю, что затратно). Однако это не означает, что невозможно или бессмысленно.
            4. Совместимость со старыми браузерами — это вообще отдельная тема, которую можно применять к любой новой технологии. Мое мнение очень простое: если мы говорим именно о фоне, пускай, и с анимациями, то старым браузерам в фоне можно показать статичную картинку со сведенными слоями.
      • UFO just landed and posted this here
          0
          before и after для фонов — это весьма жесткий хак :) Градиенты поддерживаются в IE10.
          • UFO just landed and posted this here
              +4
              В градусах жесткости (°Ж).
              • UFO just landed and posted this here
                  +2
                  П°Ж, вообще-то.
                  • UFO just landed and posted this here
          0
          SVG… Стопицот лет обратно… И не надо склеивать строку.
            0
            А есть ли способ добавлять фоновые изображения к объекту в другом селекторе?

            Only users with full accounts can post comments. Log in, please.