响应式设计

响应式设计

Media Query: 媒介查询

Device resolution (px) device-width/ device-height (px)
iPhone 320 x 480 320 x 480, in both portrait and landscape mode
iPhone 4 640 x 960 320 x 480, in both portrait and landscape mode. device-pixel-ratio is 2
iPhone 5, 5s 640 x 1136 320 x 568, in both portrait and landscape mode. device-pixel-ratio is 2
iPhone 6 750 x 1334 375 x 667, in both portrait and landscape mode. device-pixel-ratio is 2
iPhone 6 plus 1242 x 2208 414 x 736, in both portrait and landscape mode. device-pixel-ratio is 3
iPad 1 and 2 768 x 1024 768 x 1024, in both portrait and landscape mode
iPad 3 1536 x 2048 768 x 1024, in both portrait and landscape modeCSS pixel density is 2
Samsung Galaxy S I and II 480 x 800 320 x 533, in portrait modeCSS pixel density is 1.5
Samsung Galaxy S III 720 x 1280 360? x 640?, in portrait mode
HTC Evo 3D 540 x 960 360 x 640, portrait modeCSS pixel density is 1.5
Amazon Kindle Fire 1024 x 600 1024 x 600, landscape mode

有一点很奇怪,在 Safari 的 Responsive Design 设计台中,是无法使用 iPhone 系列的媒体查询的。可以在 Safari 中打开Demo页面进行测试。

常见的 iOS 机型会包括 iPhone、iPad 以及未来的 iWatch 等等。

/* ----------- iPhone 4 and 4S ----------- */

/* Portrait and Landscape */
@media only screen and (min-device-width: 320px) and (max-device-width: 480px) and (-webkit-min-device-pixel-ratio: 2) {
}

/* Portrait */
@media only screen and (min-device-width: 320px) and (max-device-width: 480px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait) {
}

/* Landscape */
@media only screen and (min-device-width: 320px) and (max-device-width: 480px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: landscape) {
}

/* ----------- iPhone 5 and 5S ----------- */

/* Portrait and Landscape */
@media only screen and (min-device-width: 320px) and (max-device-width: 568px) and (-webkit-min-device-pixel-ratio: 2) {
}

/* Portrait */
@media only screen and (min-device-width: 320px) and (max-device-width: 568px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait) {
}

/* Landscape */
@media only screen and (min-device-width: 320px) and (max-device-width: 568px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: landscape) {
}

/* ----------- iPhone 6 ----------- */

/* Portrait and Landscape */
@media only screen and (min-device-width: 375px) and (max-device-width: 667px) and (-webkit-min-device-pixel-ratio: 2) {
}

/* Portrait */
@media only screen and (min-device-width: 375px) and (max-device-width: 667px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait) {
}

/* Landscape */
@media only screen and (min-device-width: 375px) and (max-device-width: 667px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: landscape) {
}

/* ----------- iPhone 6+ ----------- */

/* Portrait and Landscape */
@media only screen and (min-device-width: 414px) and (max-device-width: 736px) and (-webkit-min-device-pixel-ratio: 3) {
}

/* Portrait */
@media only screen and (min-device-width: 414px) and (max-device-width: 736px) and (-webkit-min-device-pixel-ratio: 3) and (orientation: portrait) {
}

/* Landscape */
@media only screen and (min-device-width: 414px) and (max-device-width: 736px) and (-webkit-min-device-pixel-ratio: 3) and (orientation: landscape) {
}

HTML Boilerplate

(1)百分比下高度问题笔者习惯于在 Chrome 进行调试开发,往往在将界面放在 Android 或者 iOS 的 WebView 时候会发现部分元素在垂直方向上的尺寸出现了异常,即高度出现了异常。做两个对比:

<span data-wiz-span="data-wiz-span"
  ><div id="a" style="width: 100px; height: 200px; background-color: orange">
    <div
      id="aa"
      style="width: 100px; height: 50%; background-color: blue"
    ></div></div
></span>

其效果为黄蓝色各占一半,而如果未对父元素设置尺寸:

<span data-wiz-span="data-wiz-span"
  ><div id="b" style="width: 100px; background-color: orange">
    <div
      id="bb"
      style="width: 100px; height: 50%; background-color: blue"
    ></div></div
></span>

最终的效果就是一片空白,或者说在此状态下元素的高度由内部内容决定,譬如:

<span data-wiz-span="data-wiz-span"
  ><div id="outer">
    <div id="inner"><p>Where is pancakes house?</p></div>
  </div></span
>

outer 与 inner 两个 div 的高度会扩展到足够容纳文本内容。

Media Query: 媒介查询

Viewport

Semantic HTML

The whole “Tables vs Divs” thing just barely misses the mark. It’s not “table” or “div”. It’s about using semantic html. Even the div tag plays only a small part in a well laid out page. Don’t overuse it. You shouldn’t need that many if you put your html together correctly. Things like lists, field sets, legends, labels, paragraphs, etc can replace much of what a div or span is often used to accomplish. Div should be used primarily when it makes sense to indicate a logical division, and only appropriated for extra layout when absolutely necessary. The same is true for table; use it when you have tabular data, but not otherwise. Then you have a more semantic page and you don’t need quite as many classes defined in your CSS; you can target the tags directly instead. Possibly most importantly, you have a page that will score much better with Google (anecdotally) than the equivalent table or div-heavy page. Most of all it will help you better connect with a portion of your audience. So if we go back and look at it in terms of table vs div, it’s my opinion that we’ve actually come to the point where div is over-used and table is under-used. Why? Because when you really think about it, there are a lot of things out there that fall into the category of “tabular data” that tend to be overlooked. Answers and comments on this very web page, for example. They consist of multiple records, each with the same set of fields. They’re even stored in a sql server table, for crying out loud. This is the exact definition of tabular data. This means an html table tag would absolutely be a good semantic choice to layout something like the posts here on StackOverflow. The same principle applies to many other things as well. It may not be a good idea to use a table tag to set up a three column layout, but it’s certainly just fine to use it for grids and lists… except, of course, when you can actually use the ol or ul (list) tags.

在阅读本文的时候,反而希望你能先忘却关于 CSS、Web 开发那些你已经知道的东西,我们今天讨论的并不是多么复杂深奥的内容,如果你觉得准备好了那我们可以从下面这个简单的点图开始:

上面这些点分布的有些随意,分分合合,有近有远,我们的问题就是如何将这些点划分入到五个组中。最简单的,我们可以在那些相距较远的两个点之间设置为分隔区划分到不同的组合中。

上面这几个圈都是我随手画出来的,你当然可以选择其他的划分方式,譬如将最右边的两个点划分到一个分组中。其实这个问题并无所谓错误答案,不过如果你以如下方式划分的话:

看上去是不是觉得怪怪的?我问这个问题也不是无中生有,当我们需要为不同尺寸的屏幕设置不同的 CSS 样式稿时,会有人喜欢按照最常见的尺寸作为分割点,即 320px,768px 与 1024px。

不知道你有没有听过或者说过下面这些话:中等屏幕的话是不是按照 768px 来划分?还是应该把 768px 也划分到中等屏幕的范围内?不过这个尺寸是 iPad 横屏状态下的尺寸,应该算是大屏幕了吧?唔那大屏幕就是 768px 和以上尺寸咯?然后 320px 左右的是小屏幕?那 319px 算啥?区分蚂蚁的吗?本文的主旨即使讨论如何选择合适的分割点与分隔组。

选择合适的分隔点

你在幼儿园里就会画上面的这些圆吧,我现在用矩形度量来详细阐述下:

我们在这里选择了 600px,900px,1200px 以及 1800px 作为分割点,这些分隔组包含了最常见的 14 个机型:

我们把这两张图合并下,可以得出下面这个更适合你的老板、设计师、开发者以及测试人员看的一张图:

用更直观的方式命名你的分组

你愿意的话,也可以使用Papa-Bear 与 Baby-Bear来称呼你选定的分割点。不过当你和设计师一起讨论网站在不同屏幕上的展示效果时,肯定希望双方都能够在脑海中形成感性直观的认知。如果你用平板竖屏尺寸来形容的话,那到底是 iPad 还是 Surface 呢?特别是现在这种手机越来越大,平板越来越小的情况,你很难用单纯的平板或者手机来划分尺寸。不过好消息是苹果已经不做新产品了,他们只是不断地将按钮、耳机口从现在的产品中移除。这边我也不好给出什么建议,只能说设计师和产品之间需要多多沟通。

声明式的设置响应式布局

声明式的概念在前端很是流行,譬如著名的 React 就是典型的声明式组件库。声明式编程应用到 CSS 中即是 CSS 应当定义 What it wants,而不是 How it should。我们上面讨论过的一个关于分割点的容易混淆之处就是分割点同时代表了某个范围。譬如$large:600px这种定义在large这个词本身代表一个范围值的时候就会混淆。因此在具体的某个组件或者标签中,我们应该对其隐藏具体的尺寸设置,譬如:

@mixin for-phone-only {
    @media (max-width: 599px) {
    @content;
  }
}
@mixin for-tablet-portrait-up {
    @media (min-width: 600px) {
    @content;
  }
}
@mixin for-tablet-portait-only {
    @media (min-width: 600px) and (max-width: 899px) {
    @content;
  }
}
@mixin for-tablet-landscape-up {
    @media (min-width: 900px) {
    @content;
  }
}
@mixin for-tablet-landscape-only {
    @media (min-width: 900px) and (max-width: 1199px) {
    @content;
  }
}
@mixin for-desktop-up {
    @media (min-width: 1200px) {
    @content;
  }
}
@mixin for-desktop-only {
    @media (min-width: 1200px) and (max-width: 1799px) {
    @content;
  }
}
@mixin for-big-desktop-up {
    @media (min-width: 1800px) {
    @content;
  }
}

你会发现在上面的定义中我很推荐使用-up-only后缀,在具体使用样式的地方:

.phone-only {
    @include for-phone-only {
    background: purple;
  }
}

.tablet-portait-only {
    @include for-tablet-portait-only {
    background: purple;
  }
}

.tablet-portrait-up {
    @include for-tablet-portrait-up {
    background: purple;
  }
}

.tablet-landscape-only {
    @include for-tablet-landscape-only {
    background: purple;
  }
}

.tablet-landscape-up {
    @include for-tablet-landscape-up {
    background: purple;
  }
}

.desktop-only {
    @include for-desktop-only {
    background: purple;
  }
}

.desktop-up {
    @include for-desktop-up {
    background: purple;
  }
}

.big-desktop-up {
    @include for-big-desktop-up {
    background: purple;
  }
}

不过这种方式在需要大量自定义媒介查询搭配的时候就显得不是那么灵活,我们可以提供更加细粒度的控制方式:


@mixin for-size($size) {
  @if $size == phone-only {
  @media (max-width: 599px) { @content; }
  } @else if $size == tablet-portrait-up {
  @media (min-width: 600px) { @content; }
  } @else if $size == tablet-portait-only {
  @media (min-width: 600px) and (max-width: 899px) { @content; }
  } @else if $size == tablet-landscape-up {
  @media (min-width: 900px) { @content; }
  } @else if $size == tablet-landscape-only {
  @media (min-width: 900px) and (max-width: 1199px) { @content; }
  } @else if $size == desktop-up {
  @media (min-width: 1200px) { @content; }
  } @else if $size == desktop-only {
  @media (min-width: 1200px) and (max-width: 1799px) { @content; }
  } @else if \$size == big-desktop-up {
  @media (min-width: 1800px) { @content; }
  }
}

// usage
.my-box {
  padding: 10px;

@include for-size(desktop-up) {
  padding: 20px;
  }
}

这种方式自然能够达成预期的效果,不过如果某个粗心的开发者传入了某个未预定义的范围名,那么久尴尬了,因此我们还是建议不要传入某个具体的尺寸,而是传入某个范围:


@mixin for-size($range) {
  $phone-upper-boundary: 600px;
  $tablet-portrait-upper-boundary: 900px;
  $tablet-landscape-upper-boundary: 1200px;
  \$desktop-upper-boundary: 1800px;

@if $range == phone-only {
  @media (max-width: #{$phone-upper-boundary - 1}) { @content; }
  } @else if $range == tablet-portrait-up {
  @media (min-width: $phone-upper-boundary) { @content; }
  } @else if $range == tablet-portait-only {
  @media (min-width: $phone-upper-boundary) and (max-width: #{$tablet-portrait-upper-boundary - 1}) { @content; }
  } @else if $range == tablet-landscape-up {
  @media (min-width: $tablet-landscape-upper-boundary) { @content; }
  } @else if $range == tablet-landscape-only {
  @media (min-width: $tablet-portrait-upper-boundary) and (max-width: $tablet-landscape-upper-boundary - 1px) { @content; }
  } @else if $range == desktop-up {
  @media (min-width: $tablet-landscape-upper-boundary) { @content; }
  } @else if $range == desktop-only {
  @media (min-width: $tablet-landscape-upper-boundary) and (max-width: $desktop-upper-boundary - 1px) { @content; }
  } @else if $range == big-desktop-up {
  @media (min-width: \$desktop-upper-boundary) { @content; }
  }
}

// usage
.my-box {
  padding: 10px;

@include for-size(desktop-up) {
  padding: 20px;
  }
}
上一页