在開始前,我想先說一些話:如果你是剛接觸前端的新手,那我真的很抱歉,我真的無法在短短的幾天讓你變成切版高手,還是需要你去稍微認識一下常用的 CSS 屬性效果是什麼 😦。
那我們開始吧~
我在第一天的設計稿上做了一些結構標記,我們從大到小來看:
container 與 box
我將整個內容看成一個整體(紅框),將它命名為 container
。
container
裡面有兩個大區塊(橘框):
box1
:非歌詞區。box2
:歌詞區。container
跟 box
的排版關係:
flex-direction: row
)。flex-direction: column
)。box1
可以分成上下兩個部分:
album
:專輯圖片。info-box
:專輯資訊。album
我將專輯圖片拆成裝圖片的盒子 ( album
,藍框 ) 跟圖片本身 ( album-inner
,綠框 )
album
:專注於處理對內對外的樣式關係。album-inner
:專注於處理圖片的顯示。
object-fit: contain;
,因為專輯是一個不適合裁切或拉伸的圖片。info-box
專輯資訊(紫框)內裝著兩種不同的東西:歌名( song
)與歌手名 ( singer
)。
singer
前面有個裝飾用的矩形 ( square
)。song
跟 singer
文字對齊,square
採用 absolute
是比較方便的選擇。circle
有兩顆裝飾用的圓圈,獨立於 container
之外。
absolute
定位到對應的位置即可。我盡可能地將每個結構的排版關係都解釋一遍,不過切版方式百百種,我再寫一次可能又不一樣,所以這只是一個參考,總之能把設計稿完美實現就是好切版。
而我們主要應該關注的是每一個數值是否依照公式書寫:
calc( 設計稿上的值 / 設計稿寬度 * 100vw )
當你將設計稿上的每一個數值都按照這個公式來寫,等比縮放設計稿到網站中就能實現了,而以下是我所實現的程式碼參考:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@100..900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="./css/reset.css">
<link rel="stylesheet" href="./css/normal.css">
</head>
<body>
<div id="app">
<div class="container">
<div class="box1">
<div class="album">
<div class="album-inner">
<img src="./imgs/jay.jpg" alt="">
</div>
</div>
<div class="info-box">
<div class="song">
<div class="square"></div>
愛在西元前
</div>
<div class="singer">
周杰倫
</div>
</div>
</div>
<div class="box2 lyrics">
古巴比倫王頒布了漢摩拉比法典<br/>
刻在黑色的玄武岩 距今已經三千七百多年<br/>
妳在櫥窗前 凝視碑文的字眼<br/>
我卻在旁靜靜欣賞妳那張我深愛的臉<br/>
<br/>
祭司 神殿 征戰 弓箭 是誰的從前<br/>
喜歡在人潮中妳只屬於我的那畫面<br/>
經過蘇美女神身邊 我以女神之名許願<br/>
思念像底格里斯河般的漫延<br/>
當古文明只剩下難解的語言<br/>
傳說就成了永垂不朽的詩篇<br/>
<br/>
我給妳的愛寫在西元前 深埋在美索不達米亞平原<br/>
幾十個世紀後出土發現 泥板上的字跡依然清晰可見<br/>
我給妳的愛寫在西元前 深埋在美索不達米亞平原<br/>
用楔形文字刻下了永遠 那已風化千年的誓言 一切又重演<br/>
<br/>
祭司 神殿 征戰 弓箭 是誰的從前<br/>
喜歡在人潮中妳只屬於我的那畫面<br/>
經過蘇美女神身邊 我以女神之名許願<br/>
思念像底格里斯河般的漫延<br/>
當古文明只剩下難解的語言<br/>
傳說就成了永垂不朽的詩篇<br/>
<br/>
我給妳的愛寫在西元前 深埋在美索不達米亞平原<br/>
幾十個世紀後出土發現 泥板上的字跡依然清晰可見<br/>
我給妳的愛寫在西元前 深埋在美索不達米亞平原<br/>
用楔形文字刻下了永遠 那已風化千年的誓言<br/>
一切又重演<br/>
<br/>
我感到很疲倦離家鄉還是很遠<br/>
害怕再也不能回到你身邊<br/>
<br/>
我給妳的愛寫在西元前 深埋在美索不達米亞平原<br/>
幾十個世紀後出土發現 泥板上的字跡依然清晰可見<br/>
我給妳的愛寫在西元前 深埋在美索不達米亞平原<br/>
用楔形文字刻下了永遠 那已風化千年的誓言<br/>
一切又重演<br/>
愛在西元前<br/>
愛在西元前
</div>
</div>
<div class="circle1"></div>
<div class="circle2"></div>
</div>
</body>
</html>
css/normal.css
特別注意我每個數值都是 calc(xx / 1440 * 100vw)
或 calc(xx / 375 * 100vw)
。
:root {
--color-red: #C22A29;
}
html, body {
overflow-x: hidden;
}
body {
background-color: black;
min-height: 100vh;
min-height: 100dvh;
color: white;
font-family: "Noto Sans TC", sans-serif;
}
/* layout */
#app {
position: relative;
left: 50%;
transform: translateX(-50%);
width: 100vw;
overflow: hidden;
padding: calc(50 / 1440 * 100vw) 0;
}
.container {
width: calc(1340 / 1440 * 100vw);
margin: auto;
display: flex;
}
.box1 {
margin-right: calc(50 / 1440 * 100vw);
}
.box2 {
flex: 1;
}
@media (width < 768px) {
#app {
padding: calc(30 / 375 * 100vw) 0;
}
.container {
width: calc(335 / 375 * 100vw);
flex-direction: column;
align-items: center;
}
.box1 {
margin-right: 0;
margin-bottom: calc(20 / 375 * 100vw);
}
}
/* album */
.album {
width: max-content;
background: var(--color-red);
padding: calc(50 / 1440 * 100vw);
margin-bottom: calc(50 / 1440 * 100vw);
}
.album-inner {
position: relative;
width: calc(500 / 1440 * 100vw);
}
.album-inner::after {
content: '';
display: block;
padding-top: 100%;
}
.album img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: contain;
}
@media (width < 768px) {
.album {
padding: calc(20 / 375 * 100vw);
margin-bottom: calc(20 / 375 * 100vw);
}
.album-inner {
width: calc(260 / 375 * 100vw);
}
}
/* info */
.info-box {
padding-left: calc(30 / 1440 * 100vw);
}
.square {
position: absolute;
top: 50%;
left: calc(-10 / 1440 * 100vw);
transform: translate3d(-100%,-50%,0);
width: calc(20 / 1440 * 100vw);
height: calc(20 / 1440 * 100vw);
background-color: var(--color-red);
}
.song {
position: relative;
font-size: calc(40 / 1440 * 100vw);
font-weight: 700;
}
.singer {
font-size: calc(32 / 1440 * 100vw);
}
@media (width < 768px) {
.info-box {
padding-left: calc(20 / 375 * 100vw);
}
.square {
left: calc(-10 / 375 * 100vw);
width: calc(10 / 375 * 100vw);
height: calc(10 / 375 * 100vw);
}
.song {
font-size: calc(25 / 375 * 100vw);
}
.singer {
font-size: calc(18 / 375 * 100vw);
}
}
/* lyrics */
.lyrics {
font-size: calc(28 / 1440 * 100vw);
}
@media (width < 768px) {
.lyrics {
font-size: calc(14 / 375 * 100vw);
}
}
/* circle */
.circle1 {
position: absolute;
top: calc(862 / 1440 * 100vw);
left: calc(-100 / 1440 * 100vw);
width: calc(200 / 1440 * 100vw);
height: calc(200 / 1440 * 100vw);
border-radius: 50%;
background-color: var(--color-red);
}
.circle2 {
position: absolute;
top: calc(50 / 1440 * 100vw);
right: calc(-50 / 1440 * 100vw);
width: calc(100 / 1440 * 100vw);
height: calc(100 / 1440 * 100vw);
background: var(--color-red);
border-radius: 50%;
}
@media (width < 768px) {
.circle1 {
top: calc(-25 / 375 * 100vw);
left: calc(-25 / 375 * 100vw);
width: calc(50 / 375 * 100vw);
height: calc(50 / 375 * 100vw);
}
.circle2 {
top: calc(362 / 375 * 100vw);
right: calc(-50 / 375 * 100vw);
width: calc(100 / 375 * 100vw);
height: calc(100 / 375 * 100vw);
}
}
html, body
至少要有一個 overflow-x: hidden;
,否則 circle
定位超出視窗時會出現滾動條。
reset.css
是因為我認為這個設定本質上還是可能依據網站性質而變動,既使 99% 都會寫這個設定,我認為放進 normal.css
還是最合適的。body { min-height: 100dvh; }
:
dvh
或是 vh
會在後面某篇介紹,但這裡用到了我只好稍微說一下,簡單理解就是視窗高度的 100%。body
,預設 body
高度是頁面內容的高度,當內容高度比視窗高度小時,會導致背景色沒有完整覆蓋整個視窗,底下會有一塊白色底色。min-height: 100dvh
能讓 body
最小高度為視窗高度的 100%,這樣背景色就一定能完全覆蓋整個視窗。#app
是我習慣用來放整個網站的盒子。@media (width < ...) {...}
整個寫在一起,但我個人覺得為每個結構各別寫 @media
會是更好維護的選擇。css/reset.css
為了確保所有瀏覽器樣式一致,我們需要引入一個 reset.css
。
*,
::before,
::after,
::backdrop,
::file-selector-button {
margin: 0;
padding: 0;
border: 0;
box-sizing: border-box;
font: inherit;
color: inherit;
vertical-align: baseline;
}
:root {
font-family: system-ui, sans-serif;
-webkit-font-smoothing: auto;
-moz-osx-font-smoothing: auto;
font-optical-sizing: auto;
word-break: break-word;
-webkit-text-size-adjust: none;
-ms-text-size-adjust: none;
-moz-text-size-adjust: none;
-o-text-size-adjust: none;
text-size-adjust: none;
touch-action: manipulation;
font-feature-settings: normal;
font-variation-settings: normal;
}
/* media */
picture,
img,
svg,
video,
canvas,
iframe,
embed,
object {
display: block;
max-width: 100%;
height: auto;
image-rendering: auto;
}
source {
display: block;
}
svg {
fill: currentColor;
}
/* list */
ul,
ol,
li,
menu {
list-style: none;
}
/* link */
a {
cursor: pointer;
text-decoration: none;
}
/* table */
table {
border-collapse: collapse;
border-spacing: 0;
}
label {
display: block;
}
input,
button,
textarea,
select {
display: block;
background: none;
outline: none;
border: none;
line-height: inherit;
}
textarea {
resize: none;
}
/* button */
button,
[type="button"],
[type="reset"],
[type="submit"],
[role="button"] {
cursor: pointer;
appearance: none;
}
:disabled {
cursor: not-allowed;
}
/* dialog */
dialog {
width: auto;
height: auto;
background: none;
}
/* text */
p,
blockquote,
dd {
text-indent: 0;
}
abbr {
text-decoration: none;
}
結果
我們成功把設計稿搬到網頁上了,整個流程就只是數學公式套到底,很簡單吧。
下一篇,我想回頭來聊聊這個被我跳過的 reset.css
,以完成所謂實戰的「完整」分享。