網頁設計

第 3 章    回應式網頁設計

∗ 網頁設計

▸ 早期

✶ 使用者利用桌上或筆記電腦瀏覽網頁
✶ 網頁寬度設為固定,使所有螢幕呈現相同風格
✶ 寬螢幕左右會有空白邊界

▸ 近期

✶ 使用者利用手機與平板電腦瀏覽網頁
✶ 針對桌上型電腦所設計的網頁在手機上瀏覽會有許多問題

▸ 解決方案:

✶ 利用 HTML5 與 CSS3 做回應式網頁設計 (Responsive web design,亦稱為「響應式網頁設計」)
✶ 網頁依據不同的視域 (Viewport) 尺寸自行調整結構
# 視域:瀏覽器中可以呈現內容的區域,不包括標題、工具列、頁籤等
# 視域尺寸與螢幕尺寸不同
✶ 伺服器後端不需要做任何處理

(1) 回應式網頁設計

∗ 手機瀏覽傳統網頁的問題

▸ 需要放大或縮小頁面才能看清楚相關訊息

▸ 資料延伸太寬,需要左右滑動才能看完

▸ 按鈕或連結太小難以操作,導致常常點錯

▸ 解決方案:相同的網頁內容,但依據視域的特性做頁面結構的調整,例如:

✶ 在小視域中,將較不重要的元素移到主要項目之下
✶ 或許在更糟的狀況下:乾脆將元素隱藏,變成另一個連結
✶ 將導覽按鈕大小調整為適合手指點選的尺寸,而非固定尺寸
✶ 網頁佈局依據可讀性來縮放
✶ 資料延伸的長度彈性調整,不需左右滑動螢幕

∗ 回應式網頁 (Responsive web) 定義

▸ Ethan Marcotte 於 2010 年的 Responsive Web Design 文章中提出此名詞

▸ 整合以下三種技術,稱為回應式網頁設計 (Responsive web design)

✶ 彈性網格佈局 (Flexible grid layout)
✶ 彈性影像 (Flexible image)
✶ 媒體及媒體查詢 (Media and media queries)

▸ 其他名稱:fluid design, elastic layout, rubber layout, liquid design, adaptive layout, cross-device design, and flexible design

(2) 回應式網頁設計範例

▸ 範例:dConstruct 2011

▸ 在較寬的視域 (大於 1350px)

1350

▸ 減少視域寬度 (小於 960px):影像格網改變

960

▸ 繼續減少視域寬度 (小於 720px):頁首的連結變成小圖示,讓使用者在觸控螢幕上方便點選, 且墨綠區域從右方移至上方

720

▸ 繼續減少視域寬度 (小於 480px):影像格網改變

480

▸ 繼續減少視域寬度 (小於 320px):影像尺寸改變

320

(3) 媒體查詢

∗ 媒體查詢 (Media query)

▸ 為 CSS3 的模組之一,用來查詢使用者媒體 (Media) 的特性,以便調整 CSS 的樣式

▸ 媒體特性:視域寬度、螢幕寬高比 (Aspect ratio)、橫向 (Landscape) 或縱向 (Portrait) 等

∗ 範例:依據媒體查詢結果變換背景顏色

bgChange.html

<!doctype html>
<html>
<head>
<title>Browser background changer</title>
<meta charset="utf-8">
<link rel="stylesheet" href="css/bgChange.css">
</head>
<body>
</body>
</html>

bgChange.css

body {
  background-color: gray;
}

@media screen and (max-width: 900px) {
  body {
    background-color: red;
  }
}

@media screen and (max-width: 700px) {
  body {
    background-color: orange;
  }
}

@media screen and (max-width: 500px) {
  body {
    background-color: yellow;
  }
}

▸ 變換視域寬度則背景顏色會跟著變化

@media screen:網頁顯示媒介為螢幕 (其他選項:all, speech, print)
max-width:最大寬度

▸ CSS3 依據該裝置的能力 (Capability) 或特性 (Feature) 來決定其樣式

✶ 如果瀏覽器的特性符合條件,就使用所指定的樣式;否則就不使用
✶ 例如:依據條件載入某個樣式檔
<link rel="stylesheet" media="screen and (orientation: portrait)" href="portrait-screen.css">
# 上述有兩個問題:(1) 詢問型態:是螢幕嗎?(2) 詢問特性:是縱向嗎?如果都符合,則載入 portrait-screen.css
✶ 可加上邏輯運算 not
<link rel="stylesheet" media="not screen and (orientation: portrait)" href="portrait-screen.css">
✶ 可串接許多條件判斷
<link rel="stylesheet" media="screen and (orientation: portrait) and (min-width: 800px)" href="800wide-portrait-screen.css">

∗ 媒體特性

▸ 在建構回應式網頁時,最常查詢的是裝置的視域寬度 (width) 與螢幕寬度 (device-width)

▸ 可供查詢的特性如下:

width:視域寬度
height:視域高度
device-width:裝置的螢幕寬度
device-height:裝置的螢幕高度
orientation:螢幕方向
aspect-ratio:視域寬高比 (例如: aspect-ratio: 16/9 )
device-aspect-ratio:裝置螢幕寬高比
color:表示一個顏色的位元數 (例如: min-color: 16 )
color-index:色彩查詢表 (調色盤) 裡的顏色數量
monochrome:一個像素在單色畫幀緩衝區的位元數
resolution:螢幕或列印的解析度 (例如:min-resolution: 300dpi)
scan:針對電視,漸進式 (Progressive) 或交錯式 (Interlace) 特性,例如:720p HDTV scan: progressive ,1080i HD TV scan: interlace
grid:裝置屬於網格型 (Grid) 或位元圖型 (Bitmap)

▸ 以上名稱除了 scangrid 外, 都可以在前面加上 minmax 來產生範圍值,例如:<link rel="stylesheet" media="screen and (min-width:200px) and (max-width:360px)" href="phone.css">

(4) 範例:依據視域呈現不同的選單結構

▸ 依視域寬度調整導航按鈕的排列方式:1 列 3 列 2 行 3 列 2 行與下拉按鈕 6 列 1 行與下拉按鈕

nav3
nav4
nav6
nav7

∗ 建立 HTML 檔案

nav.html

<!doctype html>
<html>
<head>
<title>回應式導航按鈕範例</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" href="css/nav.css">
</head>
<body>

<nav>
  <ul>
    <li><a href="#">首頁</a></li>
    <li><a href="#">如何做</a></li>
    <li><a href="#">圖示</a></li>
    <li><a href="#">設計</a></li>
    <li><a href="#">Web 2.0</a></li>
    <li><a href="#">工具</a></li>
  </ul>
  <a id="pull" href="#"><img src="img/nav-icon.png"></a>
</nav>

<h3>Viewport</h3>
<p>width: <span id="width"></span></p>
<p>height: <span id="height"></span></p>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="js/screenSize.js"></script>
<script src="js/nav.js"></script>
</body>
</html>

▸ 在 <head> 元素中加入 meta viewport 標籤,用來在不同的視域中適當縮放頁面

✶ 基本格式: <meta name="viewport" content=...>
✶ 可在 content= 做許多設定,例如專為 320px 視域寬度所設計的頁面: <meta name="viewport" content="width=320">
✶ 最常用的設定:content="width=device-width, initial-scale=1, maximum-scale=1"
# 設定頁面寬度等於裝置的寬度:width=device-width
# 確保在開啟頁面時,頁面是以 1:1 的比例呈現,沒有縮放:initial-scale=1
# 防止使用者縮放頁面: maximum-scale=1

<nav> 導航區塊內含 <ul><li> 按鈕,下拉選單由 <a id="pull" ... > 控制,內含按鈕影像 nav-icon.png nav-icon

<h3>Viewport</h3>:顯示視域尺寸

▸ 最後在 </body> 之前加上 jQuery、顯示視域尺寸、及清單滑動之 JavaScript 檔案連結

∗ 建立顯示視域尺寸之程式

▸ 建立 js 子目錄,並在其中建立 screenSize.js 檔案,用來顯示視域寬度:

$(document).ready(function() {

  var $window = $(window);
  
  showViewportSize();
  $window.resize(function() {
    showViewportSize();
  });

  function showViewportSize() {
    $('#width').text($window.width());
    $('#height').text($window.height());
  }

});
nav

∗ 建立 CSS 樣式

css/nav.css

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  padding: 0;
  background-color: lightgray;
}

nav ul {
  margin: 0;
  padding: 0;
  overflow: auto;    /* 防止崩潰 */
  background-color: #485463;
}

nav li {
  list-style: none;
  width: 90px;
  float: left;
}

▸ 設定 bodynav 樣式

box-sizing:所有方框的邊線寬度均為內含
body:清除邊距與填充,背景淺灰色
nav ul:清除邊距與填充,防止崩潰,設定背景顏色
nav li:清除清單樣式,設定寬度,並且向左流動使其左右並排

▸ 結果:

nav2

▸ 設定連結樣式

...

nav li {
  ...
}

nav a {
  display: block;
  color: white;
  font-weight: bold;
  text-decoration: none;
  padding: 0.8em 0;
  width: 100%;
  text-align: center;
  border-right: 2px solid #576979; 
}

nav li:last-child a {
  border-right: none;
}

nav a:hover {
  background-color: #8c99a4;
}

a#pull {
  display: none;
}
nav a:設定區塊顯示、白色、粗體、無裝飾、上下內填充、寬度佔滿、置中對齊、右邊線、縈繞效果
✶ 隱藏 a#pull

▸ 結果:

nav3

∗ 利用媒體查詢及 jQuery 設計回應式導航按鈕

▸ 建立視域門檻:6 個導航最小總寬度為 540px (6×90px),故設此為第一門檻,視域小於 540px 時進行變化

...

a#pull {
  display: none;
}

@media screen and (max-width: 540px) {
  nav li {
    width: 50%;
  }
  
  nav a {
    border-bottom: 1px solid #576979;
    border-right: 1px solid #576979;
    text-align: left;
    text-indent: 30px;
  }
}
nav li:設寬度為 50% (每列 2 個導航)
nav a:設右、下邊線,靠左對齊,縮排

▸ 結果:

nav4

▸ 第 2 個門檻設為 480px ,導航以按鈕控制

...

@media screen and (max-width: 540px) {
  ...
}

@media screen and (max-width: 480px) {
  nav ul {
    display: none;
  }

  a#pull {
    display: block;
    background-color: #283744;
  }
}
✶ 隱藏 nav ul
✶ 顯示 a#pull (display: block;) 並設定背景顏色

▸ 結果:

nav5

▸ 在 nav.js 裡再加上 jQuery 程式

nav.js
$(document).ready(function() {

  var $menu = $('nav ul');
  
  $(document).on('click', '#pull', function() {
    $menu.slideToggle();
    return false;
  });

});

▸ 結果:

nav6

▸ 第 3 個門檻設為 360px ,導航點選後以單欄排列,亦即寬度 100%

...

@media screen and (max-width: 480px) {
  ...
}

@media screen and (max-width : 360px) {
  nav li {
    width: 100%;
  }
}

▸ 結果:

nav7

(5) 範例:「 得獎者不是 ... 」 網頁

∗ 製作類似下圖的網頁:

參考

theWinnerIsNot

∗ 所包含的結構元素:

▸ 外包裝 (Wrapper)

▸ 頁首 (Header)

▸ 上方導航 (Navigation)

▸ 側邊欄 (Sidebar)

▸ 內容 (Content)

▸ 註腳 (Footer)

structure

∗ HTML:

oscar3-1.html

<!doctype html>
<html>
<head>
<title>獎者不是...</title>
<meta charset="utf-8">
<link rel="stylesheet" href="css/normalize.css">
<link rel="stylesheet" href="css/oscar3-1.css">
</head>
<body>

<div id="wrapper">

<header>
  <p>得獎者不是...</p>
  <nav>
    <ul>
      <li><a href="#">為什麼?</a></li>
      <li><a href="#">關於</a></li>
      <li><a href="#">離線</a></li>
      <li><a href="#">救贖</a></li>
      <li><a href="#">影片</a></li>
      <li><a href="#">小考</a></li>
      <div style="clear: both;"></div>
    </ul>
  </nav>
</header>

<aside>
  <p>無名英雄 ...</p>
  <p>言過其實的廢話 ...</p>
</aside>

<div id="content">
  <p>每年我觀賞奧斯卡金像獎頒獎典禮時,都覺得很生氣 ...</p>
  <p>像金剛、紅磨坊、慕尼黑等電影都得獎,但真正的電影英雄卻都沒得獎,一點也沒有好萊塢精神,不是嗎?</p>
  <p>在這裡,我們要將事情導正。</p>
  <p>這些才應該贏得獎項 <span>&raquo;</span></p>
</div>  <!-- content -->

<footer>
  <p>注意,我們的意見是絕對正確的,你們是錯的,即使你們認為自己正確。這是事實,接受吧。</p>
</footer>

</div>  <!-- wrapper -->

</body>
</html>

∗ CSS:

oscar3-1.css

* {
  box-sizing: border-box;
  margin: 0;
}

body {
  padding: 0;
}

/* Wrapper */
div#wrapper {
  width: 960px;
  margin: 0 auto;
  background-color: #ddddff;
}

/* Header */
header {
  padding: 20px;
  background-color: #779307;
}

/* Navigation */
nav {
  padding: 20px 0;
}

nav li {
  list-style: none;
  float: left;
}

nav li a {
  display: block;
  color: black;
  padding: 10px 20px;
  text-decoration: none;
}

/* Aside */
aside {
  float: left;
  width: 240px;
  margin-top: 50px;
  padding: 20px;
  background-color: #fe9c00;
}

/* Content */
div#content {
  float: left;
  width: 720px;
  margin-top: 50px;
  padding: 20px;  
  background-color: #dedede;
}

/* Footer */
footer {
  clear: both;
  margin-top: 20px;
  padding: 20px;
  background-color: #996600;
}

▸ 網頁佈局及導航設定,並上色以資識別

✶ 所有元素:box-sizing: border-box; margin: 0;
body:清除內填充
#wrapper:寬 960px,置中
header:填充
nav:填充,將清單設定為導航連結
aside:向左流動,寬 240px,上邊距,填充
#content:向左流動,寬 720px,上邊距,填充
footer:兩邊淨空,上邊距,填充

▸ 結果:

theWinnerIsNotStructure

∗ 影像繪製:影像應該越經濟越好

▸ 頁面上下方的旗幟不應該全部繪製,而應該畫一個基礎單元然後重複背景圖 (如右下圖), headerfooter 再加上填充及雙線邊線

buntingFW buntingFWinvert

▸ 練習:將 oscar3-1.htmloscar3-1.css 分別另存新檔為 oscar3-2.htmloscar3-2.css,並如下修改:

oscar3-2.html
...
<link rel="stylesheet" href="css/oscar3-2.css">
...
oscar3-2.css
/* Header */
header {
  padding: 20px;
  padding: 60px 20px 20px 20px;
  border-bottom: 4px double #bfbfbf;
  background-image: url("../img/buntingFW.png");
  background-repeat: repeat-x;
  background-color: #779307;
}

/* Footer */
footer {
  ...
  padding: 20px;
  padding: 20px 20px 60px 20px;
  border-top: 4px double #bfbfbf;
  background-image: url("../img/buntingFWinvert.png");
  background-repeat: repeat-x;
  background-position: 0 bottom;
  background-color: #996600;
}
✶ 結果:
theWinnerIsNotStructure2

∗ 設定各類字型、圖片效果

▸ 練習:將 oscar3-2.htmloscar3-2.css 分別另存新檔為oscar3-3.htmloscar3-3.css,並如下修改:

oscar3-3.html
<link rel="stylesheet" href="css/oscar3-3.css">
✶ 設定「得獎者不是...」樣式與連結:
<p id="logo"><a href="/">得獎者<span>不是... </span></a></p>
oscar3-3.css
/* Header */
header {
  ...
}

/* Logo */
p#logo {
  font-size: 3em;
}

p#logo a {
  text-decoration: none;
}

p#logo span {
  color: lightgray;
}

/* Navigation */
...

▸ 設定左側欄:

✶ 在 HTML 設定 <h1> 標題樣式並加入圖片
<aside>
  <h1>無名英雄 ...</h1>
  <a href="#"><img class="poster" src="img/midnightRun.jpg" alt="午夜狂奔"></a>
  <a href="#"><img class="poster" src="img/wyattEarp.jpg" alt="執法捍將"></a>
  <br>
  <h1>言過其實的廢話 ...</h1>
  <a href="#"><img class="poster" src="img/moulinRouge.jpg" alt="紅磨坊"></a>
  <a href="#"><img class="poster" src="img/kingKong.jpg" alt="金剛"></a>
</aside>
# 圖片:
midnightRun.jpg   wyattEarp.jpg   moulinRouge.jpg   kingKong.jpg
✶ CSS:在 aside 之後加上字體及影像寬度之設定
/* Aside */
aside {
  ...
}

aside h1 {
  font-size: 1.2em;
}

aside img.poster {
  width: 90px;
}

/* Content */
...

#content 設定:

✶ 在 HTML 加入影像、文字環繞、設定字型效果
<div id="conten"t>
  <img id="oscar" src="img/oscar.png" alt="奧斯卡金人">
  <h1>每年<span>我觀賞奧斯卡金像獎頒獎典禮時,
    都覺得很生氣 ...</span></h1>
  <p>像<b>金剛</b><b>紅磨坊</b><b>慕尼黑</b>等電影都得獎,
    但真正的電影英雄卻都沒得獎,一點也沒有好萊塢精神,不是嗎?</p>
  <p>在這裡,我們要將事情導正。</p>
  <p><a href="#">這些才應該贏得獎項 <span>»</span></a></p>
  <div style="clear: both"></div>
</div>
# 奧斯卡金人影像
oscar
✶ CSS:設定 h1 字體尺寸及顏色,設定小金人影像之位置及尺寸:
/* Content */
div#content {
  ...
}

div#content h1 {
  font-size: 4em;
}

div#content h1 span {
  display: block;
  color: #757474;
  font-size: 0.6em;
}

div#content img#oscar {
  float: left;
  margin-top: -50px;
  width: 200px;
}
✶ 結果:
isnot

▸ 當視域寬度小於 960px 時,右方內容會被截斷

(4) 防止行動瀏覽器自動調整頁面大小

∗ 覆蓋預設之視域設定

▸ iOS 與 Android 瀏覽器均奠基在 Webkit 技術上,這些瀏覽器 (以及 Opera) 允許使用特定的 <meta> 視域元素 (置於 <head> 元素中) 來覆蓋原先的設定,例如:

✶ 可設定固定寬度或者縮放的比例:設定視域寬度為裝置的寬度,而且頁面以 2 倍大小呈現
<meta name="viewport" content="initial-scale=2.0, width=device-width">
✶ 可設定使用者可縮放的程度:設定縮放範圍為 0.5 到 3 倍
<meta name="viewport" content="width=device-width, maximum-scale=3, minimum-scale=0.5">
✶ 可禁止縮放:
<meta name="viewport" content="initial-scale=1.0, user-scalable=no">
✶ 註:亦可將 <meta> 設定以下列格式放在 CSS 檔中,而不要放在 HTML 檔中
@viewport {
  width: 320px;
}

∗ 利用媒體查詢,對於小視域改變元素呈現之先後順序

▸ 練習:將 oscar3-3.htmloscar3-3.css 分別另存新檔為 oscar3-4.htmloscar3-4.css,並如下修改:

▸ HTML:

✶ 修改 CSS 連結
<link rel="stylesheet" href="css/oscar3-4.css">
✶ 控制頁面之尺寸,使頁面無法縮放,因此可以將頁面設定固定下來:在 HTML 的 <head> 元素中加入以下:
<meta name="viewport" content="initial-scale=1.0, width=device-width, user-scalable=no">

▸ CSS:刪除各區塊背景顏色

▸ 進行回應式設計

✶ 理念:在小視域的狀況下,應該讓重要的資訊先出現,因此,在 HTML 中 #content 應放在 aside 之前
# HTML:調換 #contentaside 的順序 (CSS 兩元素的設定亦調換)
...

<div id="content">
  ...
</div>


<aside>
  ...
</aside>

...
# 但桌上型頁面就會變成 aside 在右邊 #content 在左邊,解決方案:將 #content 往右流動
CSS:
/* Content */
div#content {
  float: right;
  ...
}
✶ CSS 再加上媒體查詢:設定門檻為 800px,設定 #wrapper 寬 800px, #content 設定無流動,寬 100%
/* Footer */
footer {
  ...
}

/* Responsive design */
/* At 800px */
@media screen and (max-width: 800px) {
  div#wrapper {
    width: 800px;
  }
  
  div#content {
    float: none;
    width: 100%;
  }
}
aside:設定無流動,且讓影像改為左右並列
# HTML:改為兩個 h1 區塊,以便在 aside 裡設定文字置中,第一個 h1 加上 id 以便設定右邊距
<aside>
<h1 id="first">
<p>無名英雄 ...</p>
<a href="#"><img class="poster" src="img/midnightRun.jpg" alt="午夜狂奔"></a>
<a href="#"><img class="poster" src="img/wyattEarp.jpg" alt="執法捍將"></a>
</h1>
<h1>
<p>言過其實的廢話</p>
<a href="#"><img class="poster" src="img/moulinRouge.jpg" alt="紅磨坊"></a>
<a href="#"><img class="poster" src="img/kingKong.jpg" alt="金剛"></a>
</h1>
</aside>
# CSS:將 aside 寬度設為 100% 並停止流動,h1 設定為 inline-block ,且第一個 h1 設定右邊距,放大海報尺寸
/* Responsive design */
/* At 800px */
@media screen and (max-width: 800px) {
  ...
  div#content {
    ...
  }
  
  aside {
    float: none;
    width: 100%;
    text-align: center;
  }
  
  aside h1 {
    display: inline-block;
  }
  
  aside h1#first {
    margin-right: 50px;
  }
  
  aside img.poster {
    width: 120px;
    height: 180px;
  }
}
✶ 測試:因小金人影像過高,超出 content 區塊範圍, 海報影像被向右擠壓,解決方案:加上淨空區塊,讓 content 區塊包含小金人影像
<div id=content>
  ...
  <p>這些才應該贏得獎項 <span>»</span></p>
  <div style="clear:both"></div>
</div> <!--content -->
✶ 結果:
isnot2

∗ 媒體查詢機制解決了所有問題?沒有!

▸ 目前媒體查詢所設定的視域門檻還不夠,低於 800px 視域會有截斷情形,在 800px ~ 960px 之間的視域也會有截斷情形

▸ 問題原因:當視域到達媒體查詢門檻,頁面佈局會變化,但當視域在媒體查詢的門檻之間, 佈局是維持固定的

▸ 解決方案:將固定佈局改為流動佈局