Inset box-shadow dla body

Co zamierzamy osiągnąć?

Chcemy uzyskać wewnętrzne cienie dla całego body, takie jak na poniższej grafice:

Zakładany efekt

Podejście pierwsze (nieoptymalne)

Zabieramy się zatem do pracy. Na początek potrzebujemy tekstury dla body:

Tekstura tła dla body

Następnie przechodzimy do stylowania w CSS. Nasze style będą wyglądały następująco:

CSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
body{
background: #404040 url('bg.png') repeat 0 0;
background-attachment: fixed;
position: relative;
}
body:after{
content: "";
display: block;
box-shadow: #000000 15px 15px 75px inset, #000000 -15px -15px 75px inset;
position: fixed;
top: 0;
bottom: 0;
right: 0;
left: 0;
z-index: -1;
}

Co się dzieje w powyższym kodzie? Ustawiamy dla body obrazek tła oraz background-attachment: fixed.

Właściwość background-attachment określa czy obrazek w tle jest stały czy też przewija się wraz z zawierającym go elementem.

Następnie korzystając z pseudo elementu :after, dla którego ustawiamy box-shadow, tworzymy wewnętrzny cień dla body. Nasz pseudo element rozciągnięty jest na całą powierzchnię body za pomocą właściwości top, right, bottom oraz left, ustawionych na 0. Jednak aby takie rozciągnięcie działało prawidłowo, musimy jeszcze dodać position: fixed. Dzięki temu nasz cień nie będzie się przewijał razem ze stroną. Na koniec dodajemy z-index: -1, co sprawi, że cień nie będzie nam przykrywał treści strony, tylko będzie znajdował się pod nią.

Wydawać by się mogło, że to wystarczy do otrzymania zamierzonego efektu w nowoczenych przeglądarkach. Niestety, powyższy przykład działa strasznie topornie, pożerając masę zasobów. Dowód przedstawiam poniżej:

Wpis z forum, sygnalizujący problem.

Spróbujcie odpalić sobie demo w Google Chrome.

zobacz demo

Jak zatem poradzić sobie z tym problemem?

Podejście drugie – obrazki na ratunek

Tym razem zmieniamy podejście. Rezygnujemy ze stosowania właściwości box-shadow na rzecz obrazków. A zatem do dzieła.

Na początku przygotowujemy w Photoshopie cień:

Cień przygotowany w Photoshopie

Następnie wycinamy potrzebne fragmenty, które wykorzystamy przy stylowaniu:

Wycinamy potrzebne fragmenty

Od strony HTML’a będziemy potrzebowali trzech elementów opakowujących całą treść strony – zaraz po body. Ja nazwałem je #body-wrapper, #body-inner i #body-content, tak więc struktura naszego HTML’a będzie następująca:

HTML
1
2
3
4
5
6
7
8
9
<body>
<div id="body-wrapper">
<div id="body-inner">
<div id="body-content">
<!-- content strony -->
</div>
</div>
</div>
</body>

Mając tak przygotowaną strukturę HTML’ową, przechodzimy od stylowania. Wykorzystamy w tym celu potęgę pseudo elementów:

Rozmieszczenie poszczególnych pseudo elementów
CSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
body{
background: #404040 url(bg.png) repeat 0 0;
background-attachment: fixed;
position: relative;
}
body:after{
content: "";
width: 80px;
height: 80px;
display: block;
background: url(tl-shadow.png) no-repeat 0 0;
position: fixed;
top: 0;
left: 0;
z-index: -1;
}
body:before{
content: "";
width: 80px;
height: 80px;
display: block;
background: url(tr-shadow.png) no-repeat 0 0;
position: fixed;
top: 0;
right: 0;
z-index: -1;
}
#body-wrapper:after{
content: "";
width: 80px;
height: 80px;
display: block;
background: url(bl-shadow.png) no-repeat 0 0;
position: fixed;
bottom: 0;
left: 0;
z-index: -1;
}
#body-wrapper:before{
content: "";
width: 80px;
height: 80px;
display: block;
background: url(br-shadow.png) no-repeat 0 0;
position: fixed;
bottom: 0;
right: 0;
z-index: -1;
}
#body-inner:after{
content: "";
height: 80px;
display: block;
background: url(bh-shadow.png) repeat-x 0 0;
position: fixed;
bottom: 0;
right: 80px;
left: 80px;
z-index: -1;
}
#body-inner:before{
content: "";
height: 80px;
display: block;
background: url(th-shadow.png) repeat-x 0 0;
position: fixed;
top: 0;
right: 80px;
left: 80px;
z-index: -1;
}
#body-content:after{
content: "";
width: 80px;
display: block;
background: url(rv-shadow.png) repeat-y 0 0;
position: fixed;
top: 80px;
bottom: 80px;
right: 0;
z-index: -1;
}
#body-content:before{
content: "";
width: 80px;
display: block;
background: url(lv-shadow.png) repeat-y 0 0;
position: fixed;
top: 80px;
bottom: 80px;
left: 0;
z-index: -1;
}

W powyższym kodzie rozmieszczamy pseudo elmenty według schematu i ustawiamy odpowiednie orazki jako tło. Wszystkie elementy muszą mieć position: fixed, aby nie przewijały się razem z resztą strony.

Na koniec pozbywamy się elementów #body-wrapper, #body-inner i #body-content z html’a i przenosimy je do JavaScript. Korzystając z jQuery opakowujemy całą zawartość strony w/w div-ami.

JavaScript
1
2
3
4
5
$(document).ready(function(){
$('body').wrapInner( '<div id="body-content" />' );
$('#body-content').wrap('<div id="body-wrapper" />');
$('#body-content').wrap('<div id="body-inner" />');
});

zobacz demo

Podsumowanie

Efekt końcowy w obu przypadkach jest taki sam. Różnica polega na tym, że implementacja drugiego sposobu wymaga trochę więcej czasu i kombinacji, niż podejście pierwsze. Niestety na chwilę obecną trzeba jeszcze nieco ostrożnie podchodzić do CSS3, ponieważ – tak jak w opisanym przykładzie – można się wpakować na minę.