简单聊下响应式布局

简单的从响应式布局的viewport介绍到读hexo博客的next主题的源码,来介绍下响应式布局。

viewport

谈到响应式,就离不开viewport。

移动端浏览器在一个通常比屏幕更宽的虚拟“窗口”(视口)中渲染页面,用户从而无须将所有页面都压缩进一个小屏幕里(那样会把很多没有针对移动端进行优化的站点打乱)。用户可以通过平移和缩放来浏览页面的不同区域。

两种viewport

  • 布局视口(layout viewport)
  • 视觉视口(visual viewport)

想象一下布局视口是一个足够大的而且不会改变尺寸和形状的图片,现在想象你有一个很小的框,你通过这个框来看上面那个足够大的图片。小框周围布满了不透明的材料,通过小框看到的大图就是视觉视口。你可以将小框向后移来观看所有的大图,也可以将小框靠近大图来观看大图的一部分。你也可以改变小框的方向,不过你要知道大图(布局视口)的形状和大小是从来没有改变的。

视觉视口(visual viewport)是在当前页面中显示屏幕的一部分。用户可能会滚动来改变他所看到的页面的一部分,或者通过缩放来改变视觉视口的尺寸。

然而,CSS布局,尤其是百分比宽度,是相对于布局视口计算的。布局视口比视觉视口宽的多。

因此<html>元素最初获取布局视口的宽度,而且你的CSS被解释为宽度比手机宽的多,就相当于上面比喻中小框远离视觉视口的大图。这样可以确保你的页面布局的行为和桌面是一样的。

不同的浏览器用的默认layoutview的大小是多少呢?

  • Safari iPhone 使用980px
  • Opera 使用850px
  • Android WebKit 使用800px
  • IE 使用974px

在桌面端viewport

桌面端有一个有趣的现象,如果我们定义body中的子元素width:100%,那么这100%相对于谁呢?对,相对于html,html的宽度又取决于viewportviewport的宽度又相当于浏览器窗口的宽度。

上面的情况在100%zoom的时候显示正常,当我们缩放窗口的时候,viewport小于页面的总宽度。页面本身没有关系,内容现在溢出超过<html>,但是元素属性设置了overflow: visible,这边是超出的内容也会展示出来。

下例中,蓝色的顶部栏由于设置了width: 100%,所以浏览器会遵循viewport所设置的宽度。这样蓝色的顶部栏并不管现在它太狭窄了。

缩放zooming

两种视口都是用来测量CSS像素,但是显然视觉视口的尺寸是根据缩放改变的(如果你放大,屏幕上的像素就会变少)。如果布局视口没有一直保持不变,那么百分比计算的宽度将会回流和重新计算。

了解布局视口

为了了解布局视口,我们需要关注一下页面完全缩小的情况。很多移动浏览器都是在完全缩放的情况下展示页面。这种情况下视觉视口等于布局视口。

因此布局视口的宽度和高度等于我们在完全缩放模式看到的。即使当用户放大窗口,布局视口也保持不变。

而且当你旋转手机到水平的时候,布局视口的内容也没有变,依旧是缩放到手机上面。这种情况有会导致一个问题,高度比横向少得多,不过网页开发者一般不关心页面的高度,只关心宽度。

下面罗列几个布局视口的宽度

属性 含义
document. documentElement. clientWidth / Height 布局视口尺寸
window.innerWidth/Height 视觉视口尺寸
screen.width and screen.height 屏幕尺寸
window.pageX/YOffset 滚动偏移,和视觉视口相对于布局视口的值相同
document. documentElement. offsetWidth / Height <html>标签的尺寸
Media queries 测量<html>的宽度(width)或者设备的宽度(device-width)。

viewport Meta属性

这里让我们看一下<meta name="viewport" content="width=320">的意义,这表示调整布局视口的尺寸。为了了解为什么这个属性这么重要,我们需要退后一步讨论。

假设你正在写一个简单的页面而且你没有设定你的元素的宽度。那么他们就会伸缩到布局视口宽度的100%。大多数浏览器都会将整个页面缩放来显示整个布局视口。像下图所示和缩放效果。

现在设置html {width: 320px}的时候,<html>将会收缩,所有的元素都会取320px的100%,但是这种情况只有用户放大页的时候才能得到,并不是初始化的状态。当用户缩小页面的时候,将会有大片的空白区域。

当你设置为<meta name="viewport" content="width=320">的时候,初始化也显示正常了。你也可以设置任何你想要的宽度,包括device-width。将会取得屏幕的宽度screen.width(在设备像素中),于是布局视口将会使用这个值。

不过这里有一个问题,有时真正的screen.width并没有起多大的作用,因为像素树太高了。例如, Nexus One 的屏幕像素是480px,不过谷歌工程师觉得给480px的宽度作为device-width来说太大了。所以他们决定使用缩放到 2/3 ,所以device-width所表达的宽度就是320px。

最后这里介绍下典型的针对移动端优化的viewport

1
2
3
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<!-- 添加用户缩放禁止 -->
<meta name="viewport" content="width=device-width, initial-scale=1,maximum-scale=1, minimum-scale=1, user-scalable=no">

width控制视口的宽度。可以设置width=600这样的固定值,或者device-width这类的特殊值来指导比例为100%时屏幕宽度的CSS像素数值。

像素并非像素

许多手机都有物理像素都比页面的布局大得多,所以移动端将会以多个物理像素现已单个CSS像素。意味着initial-scale=1在安卓或者ios手机上,都能显示较为接近的物理尺寸。

在240dpi及以上的屏幕上,initial-scale=1的页面实际上会被Android WebKit浏览器放大至150%。其中的文字会保持平滑锐利,但是位图图像在全屏模式下就会不尽人意。为了使图片在这些屏幕上变得清晰,web开发者会将图片甚至整个布局设计成最终尺寸的150%(或者200%从而支持像配备retina屏的iPhone那样的像素密度高达320 dpi及以上的设备),然后通过CSS或视口属性缩小。

默认比例依赖于显示密度。在密度低于200dpi的显示设备上,比例为1.0。在密度介于200及300dpi之间的显示设备上,比例为1.5。对于具有300dpi以上密度的现实设备,比例为/150dpi向下取整。注意再有在视口比例为1时才会应用默认比例。否则,CSS像素与设备像素之间的关系依赖于当前的缩放等级。

next源码中响应式应用

hexo中的next主题是一个非常热门的blog主题,其中的相关源码是使用stylus书写的。

首先在文件夹外面定义了尺寸,这部分尺寸定义在需要修改样式的地方会进行修改。

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
mobile-smallest() {
@media (max-width: 413px) {
{block}
}
}

mobile-small() {
@media (max-width: 567px) {
{block}
}
}

mobile() {
@media (max-width: 767px) {
{block}
}
}

tablet() {
@media (min-width: 768px) and (max-width: 991px) {
{block}
}
}

desktop() {
@media (min-width: 992px) {
{block}
}
}

desktop-large() {
@media (min-width: 1600px) {
{block}
}
}

博客的左侧的页面导航在小于767的时候会隐藏,在大于767px的时候会在左上显示。这里展示部分代码:

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
.site-nav-toggle {
display: none;
position: absolute;
top: 10px;
left: 10px;
+mobile() {
display: block;
}

button {
margin-top: 2px;
padding: 9px 10px;
background: transparent;
border: none;
}
}

.site-nav {
+mobile() {
display: none;
margin: 0 -10px;
padding: 0 10px;
clear: both;
border-top: 1px solid $gray-lighter;
}
+tablet() { display: block !important; }
+desktop() { display: block !important; }
}

最后放下layout的布局stylus:

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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
.header {
position: relative;
margin: 0 auto;
width: $main-desktop;

+tablet() {
width: auto;
}
+mobile() {
width: auto;
}
}

.header-inner {
position: absolute;
top: 0;
overflow: hidden;
padding: 0;
width: 240px;
background: white;
box-shadow: $box-shadow-inner;
border-radius: $border-radius-inner;

+desktop-large() {
.container & { width: 240px; }
}
+tablet() {
position: relative;
width: auto;
border-radius: initial;
}
+mobile() {
position: relative;
width: auto;
border-radius: initial;
}
}

.main {
clearfix();
+tablet() {
padding-bottom: 100px;
}
+mobile() {
padding-bottom: 100px;
}
}

.container .main-inner {
width: $main-desktop;

+tablet() {
width: auto;
}
+mobile() {
width: auto;
}
}

.content-wrap {
float: right;
box-sizing: border-box;
padding: $content-desktop-padding;
width: $content-desktop;
background: white;
min-height: 700px;
box-shadow: $box-shadow-inner;
border-radius: $border-radius-inner;

+tablet() {
width: 100%;
padding: 20px;
border-radius: initial;
}
+mobile() {
width: 100%;
padding: 20px;
min-height: auto;
border-radius: initial;
}
}

.sidebar {
position: static;
float: left;
margin-top: 300px;
width: $sidebar-desktop;
background: $body-bg-color;
box-shadow: none;

+tablet() {
display: none;
}
+mobile() {
display: none;
}
}

.sidebar-toggle { display: none; }


.footer-inner {
width: $main-desktop;
padding-left: 260px;

+tablet() {
width: auto;
padding-left: 0 !important;
padding-right: 0 !important;
}
+mobile() {
width: auto;
padding-left: 0 !important;
padding-right: 0 !important;
}
}



.sidebar-position-right {
.header-inner { right: 0; }
.content-wrap { float: left; }
.sidebar { float: right; }

.footer-inner {
padding-left: 0;
padding-right: 260px;
}
}

小结

因为blog的主要受众还是网页端,所以默认尺寸的填写时桌面端,再根据媒体查询开始往小尺寸开始。我看网上有说,页面优先从小尺寸开始写,这样比较适合响应式。不过我觉得应该根据页面的主要受众,来决定是优先移动端还是桌面端。太复杂的页面应该也不用写响应式,应该根据桌面端和移动端单独设计。

引用

评论

加载中,最新评论有1分钟延迟...