感謝 iT 邦幫忙與博碩文化,本系列文章已出版成書「從 Hooks 開始,讓你的網頁 React 起來」,首刷版稅將全額贊助 iT 邦幫忙鐵人賽,歡迎前往購書,鼓勵筆者撰寫更多優質文章。
老實說這幾天在寫鐵人賽時,發現花最多時間的常常是在找自己喜歡的設計畫面,因為喜歡好看的作品,但卻又不會設計,同時也不想要只是用簡陋的畫面說明程式語法...。好吧!廢話完畢,希望你會喜歡這 30 天中自己做出來的作品。
這次即時天氣的設計畫面主要是參考 imgur 上的圖片,另外則會使用 The Weather is Nice Today 提供的天氣 ICON 來完成。完成的畫面大概會像下面這樣:
昨天已經把用來撰寫 Styled Components 的套件 emotion 載入專案當中,在 CodeSandbox 中你一樣可以透過 Fork 複製一份昨天的程式碼 Weather App - started template 在繼續開始今天的內容:
這裡我們根據下圖拆分成不同的 HTML 區塊:
在 Emotion 中基本 styled components 的使用如同昨天建立 <Container />
和 <WeatherCard />
的做法,這裡我們就繼續完成 <WeatherCard />
裡面的結構和樣式,順便複習一下。
以 Location 這個區塊為例,我們預期它會是個 div
元素,因此要建立帶有樣式的組件,只需要:
// 定義帶有樣式的 `<Location />` 組件
// 在兩個反引號中放入該 Component 的 CSS 樣式
const Location = styled.div`
font-size: 28px;
color: #212121;
margin-bottom: 20px;
`;
定義好之後,它就是一個 React 組件,可以直接把 <Location />
放入 JSX 中:
而它最後在 HTML 中呈現出來就會是帶有一個特殊 class name 的 <div>
,這個 class name(即下圖中的 css-a7vwns
)則會對應到剛剛針對 <Location>
所撰寫的 CSS 樣式:
上面是用 Emotion 建立 Styled Components 最基本的方式。現在就讓我們把其他的 Styled Components 完成,如果你對於 CSS 的內容沒有太大興趣的話,可以直接把下面的程式碼複製到 CodeSandbox 中的 ./src/WeatherApp.js
這隻檔案:
import React from 'react';
import styled from '@emotion/styled';
const Container = styled.div`
background-color: #ededed;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
`;
const WeatherCard = styled.div`
position: relative;
min-width: 360px;
box-shadow: 0 1px 3px 0 #999999;
background-color: #f9f9f9;
box-sizing: border-box;
padding: 30px 15px;
`;
const Location = styled.div`
font-size: 28px;
color: #212121;
margin-bottom: 20px;
`;
const Description = styled.div`
font-size: 16px;
color: #828282;
margin-bottom: 30px;
`;
const CurrentWeather = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
`;
const Temperature = styled.div`
color: #757575;
font-size: 96px;
font-weight: 300;
display: flex;
`;
const Celsius = styled.div`
font-weight: normal;
font-size: 42px;
`;
const AirFlow = styled.div`
display: flex;
align-items: center;
font-size: 16x;
font-weight: 300;
color: #828282;
margin-bottom: 20px;
`;
const Rain = styled.div`
display: flex;
align-items: center;
font-size: 16x;
font-weight: 300;
color: #828282;
`;
const WeatherApp = () => {
return (
<Container>
<WeatherCard>
<Location>台北市</Location>
<Description>多雲時晴</Description>
<CurrentWeather>
<Temperature>
23 <Celsius>°C</Celsius>
</Temperature>
</CurrentWeather>
<AirFlow>23 m/h</AirFlow>
<Rain>48%</Rain>
</WeatherCard>
</Container>
);
};
export default WeatherApp;
此時你應該會看到如下的畫面:
目前還未載入任何和天氣有空 ICON。
這裡為了讓圖片能夠擁有最佳的解析度,關於天氣的圖示我們會使用 SVG 而非 PNG,SVG 屬於向量圖,因此不論縮放到多大的尺寸都可以維持最佳的解析度,而不會出現像是 JPG 或 PNG 放到過大後出現方格的情形。
這裡使用的天氣圖示都來自 The Weather is Nice Today,其中因為有些 SVG 直接上傳到 CodeSandbox 呈現時有些問題,所以會先透過 Sketch 重新轉檔,轉檔後的圖片會放在 dropbox 上提供下載,你可以直接透過拖曳的方式,拉到 CodeSandbox 中。
先在 ./src
資料夾中新增一個 images
的資料夾,接著把 dropbox 上的圖檔下載到本機後,可以透過拖曳的方式把它們拉進去 images
資料夾中:
Dropbox 內的天氣圖示會隨專案使用持續增加,但下載連結不會改變,有新圖示時可以在重新下載。
由於在 CodeSandbox 中是透過 create-react-app 這個工具建立起的 React 開發環境,相關的配置都已經設定,所以要把 SVG 載入 React 中的方式很簡單。
關於 create-react-app 這個建立專案的工具會在最後幾天說明,只需先知道透過這個工具可以快速建立好 React 的開發環境。
第一種方式是把 SVG 當成一個 React 組件加以載入,因為變成了 React 組件,所以後續如果有需要改變 SVG 的顏色或做動畫都比較靈活。
寫法像這樣:
./images/cloudy.svg
匯入,並將該組件命名為 Cloudy
,而 ReactComponent
是 create-react-app 提供的物件<Cloudy />
// STEP 1:使用 import { ReactComponent as xxx } from xxx 載入 SVG
import { ReactComponent as Cloudy } from './images/cloudy.svg';
import { ReactComponent as RainIcon } from './images/rain.svg';
const App = () => (
<div>
{/* STEP 2:直接使用該 Component */}
<Cloudy />
<RainIcon />
</div>
);
這種方法因為是把 SVG 以圖檔的形式載入,因此後續比較難去修改 SVG 圖示的顏色、粗細或製作動畫等效果,但若單純只是要以圖檔呈現,使用這種方式較簡便:
./images/cloudy.svg
匯入,匯入的內容會變成該圖檔的路徑<img src={...} />
的方式將 SVG 圖片掛入// STEP 1:使用 import xxx from xxx 載入 cloudyIcon,會取得該圖檔路徑
import cloudyIcon from './images/cloudy.svg';
import rainIcon from './images/rain.svg';
const App = () => (
<div>
{/* STEP 2:透過 src 把 SVG 圖片呈現出來 */}
<img src={cloudyIcon} alt="cloudy icon" />
<img src={rainIcon} alt="rain icon" />
</div>
);
⚠️ 注意:上述這兩種載入 SVG 圖檔的方式都需要使用 create-react-app 來建立專案,或在 WebPack 中有對應的設定才可以。
由於上述第二種直接 import SVG 圖片的方式實際上是取得該圖檔的路徑,但在 CodeSandbox 中該圖檔路徑會是錯誤的,進而有破圖的情況,因此在後面的範例中我們統一都會使用上述的第一種方式。
現在就讓我們來把相關的圖片載入進來:
// ./src/WeatherApp.js
import React from 'react';
// 載入圖示
import { ReactComponent as CloudyIcon } from './images/cloudy.svg';
import { ReactComponent as AirFlowIcon } from './images/airFlow.svg';
import { ReactComponent as RainIcon } from './images/rain.svg';
import { ReactComponent as RedoIcon } from './images/redo.svg';
// ...
接下來就可以把這些圖式直接當成 Component 來使用,放入 JSX 中:
現在的畫面會像這樣子:
圖片帶進來後,因為沒有設定高,破版的有點嚴重。
對於在些 SVG 的組件來說,最後渲染到網頁的時候其實就是把 SVG 的程式碼放入 HTML 內,因此一樣透過 CSS 選擇器去選到對應的 SVG 後進行樣式的調整。這裡我們先調整一下 <AirFlow />
和 <Rain />
的部分。只須在當初定義 styled components 的地方去添加 CSS 修改 SVG 的樣式即可:
// ./src/WeatherApp.js
import React from 'react';
// ...
const AirFlow = styled.div`
display: flex;
align-items: center;
font-size: 16x;
font-weight: 300;
color: #828282;
margin-bottom: 20px;
svg {
width: 25px;
height: auto;
margin-right: 30px;
}
`;
const Rain = styled.div`
display: flex;
align-items: center;
font-size: 16x;
font-weight: 300;
color: #828282;
svg {
width: 25px;
height: auto;
margin-right: 30px;
}
`;
const WeatherApp = () => {
// ...
};
export default WeatherApp;
其中修改的部份如下:
現在的畫面會長得像這樣子:
可以看到在風速和雨量的部分大小已經調整好了,但我們還希望天氣的圖示還有「重新整理」的符號可以在小一點,我們接著來看可以怎麼做。
前面我們提到過怎麼使用 Emotion 來建立帶有樣式的 styled components,但 Emotion 不僅可以用來建立組件,還可以將原本就存在的組件添加樣式。
舉例來說,剛剛我們透過 import
載入的 SVG 是一個 React 組件,例如,<CloudyIcon />
和 <RedoIcon />
。現在如果我們想要為這個原本就存在的組件添加樣式時,可以這麼做:
// 透過 styled(組件) 來把樣式帶入已存在的組件中
const Cloudy = styled(CloudyIcon)`
/* 在這裡寫入 CSS 樣式 */
flex-basis: 30%;
`;
const Redo = styled(RedoIcon)`
/* 在這裡寫入 CSS 樣式 */
width: 15px;
height: 15px;
position: absolute;
right: 15px;
bottom: 15px;
cursor: pointer;
`;
也就是說,原本是在 styled.
後面加上一個 HTML 標籤,現在則是放入一個 React 組件,然後就可以在裡面撰寫 CSS 樣式。
現在我們就把上面透過 Emotion 添加完樣式後的新組件放到 JSX 中,也就是把原本的 <CloudyIcon />
改成 <Cloudy />
,原本的 <RedoIcon />
改成 <Redo />
:
大功告成,終於做出我們像要的即時天氣 App 畫面!
明天我們會開始串接中央氣象局的資料,讓畫面不再只是假新聞!
今天完整的程式碼一樣可以到 CodeSandbox 中檢視 Weather APP with Emotion。
既然透過 Emotion 建立的 Styled Components 中仍然是個 React 的組件,就表示可以像第 11 天的內容一樣,透過 props 把資料傳入組件內。
舉例來說,現在如果想要將資料 theme
帶入建立好的 <Location />
內,一樣只要利用像是 HTML 屬性的方式即可:
const WeatherApp = () => {
return (
{/* ... */}
<Location theme="dark">台北市</Location>
{/* ... */}
);
};
接著在使用 Emotion 定義 Location
這個 Styled Component 的地方,就可以透過 props
取得傳入的資料:
// 透過 props 取得傳進來的資料
// props 會是 {theme: "dark", children: "台北市"}
const Location = styled.div`
${props => console.log(props)}
font-size: 28px;
color: #212121;
margin-bottom: 20px;
`;
這個做有什麼用呢?當我們可以取得外部傳進來的資料時,就可以根據這個資料來決定要呈現的 CSS 樣式,例如,當 theme
為 dark
時就把文字顏色改成 #DADADA
,否則顯示 #212121
,就可以寫成這樣:
// 透過傳進來的資料決定要呈現的樣式
const Location = styled.div`
font-size: 28px;
color: ${props => props.theme === 'dark' ? '#dadada' : '#212121'};
margin-bottom: 20px;
`;
有時多個組件間還是可能有需要被共用的樣式,像是如果每個按鈕都有固定的外觀,只是不同按鈕組件的顏色有不同時,如果重複在每個按鈕組件都撰寫同樣的 CSS 樣式會變得有點多餘,而且若之後需要修改按鈕的外觀,還得要每支檔案一支一支改,但卻又不想定義 class 樣式來套用時,可以怎麼做呢?
在 Emotion 中可以把撰寫好的 CSS 樣式當作 JavaScript 函式保存起來,步驟如下:
@emotion/core
中匯入 Emotion 提供的 css
函式// STEP 1:匯入 Emotion 提供的 css 函式
import { css } from '@emotion/core';
// STEP 2:將一批 CSS 樣式定義成 JavaScript 函式
const buttonDefault = () => css`
display: block;
width: 120px;
height: 30px;
font-size: 14px;
background-color: transparent;
color: #212121;
`;
// STEP 3 在定義 Styled Components 時載入定義好的 CSS 樣式
// 和 CSS 一樣,同樣的樣式後面寫的會覆蓋前面寫的
const rejectButton = styled.button`
${buttonDefault}
background-color: red;
`
const acceptButton = styled.button`
${buttonDefault}
background-color: green;
`
這種作法不僅能夠讓組件共用某些樣式,一樣能夠透過 props
取得組件帶進來的資料,當有需要切換亮色/暗色主題的時候非常實用:
import { css } from '@emotion/core';
// 在共用樣式的函式中,一樣可以透過 props 取得外部傳來的資料
const buttonDefault = (props) => css`
display: block;
width: 120px;
height: 30px;
font-size: 14px;
background-color: transparent;
color: ${props.theme === 'dark' ? '#dadada' : '#212121'};
`;
const rejectButton = styled.button`
${buttonDefault}
background-color: red;
`;
const acceptButton = styled.button`
${buttonDefault}
background-color: green;
`;
當然 Emotion 的用法還不只這些,這裡只提到一些蠻常用也蠻實用的一些功能,若想完整了解 Emotion 的更多功能,可以進一步參考 Emotion 官方文件。
功能完成之後,當然要美美的呈現阿
function work 正常之後 開始進行拉皮、化妝
然後...可能 拉皮的時間變成 function 的時間的兩倍...
我覺得你真的說到我的心坎裡了
後面變成花很多間在處理資料XDD
看來您也是..文章很精美 內容很豐富
然後PO上來時
這邊調一下 那邊空一格
這個要對齊
恩...在多個表情好惹
恩...不行這邊要加一張圖
.
.
.
然後一天又過去了 感謝飛天小女警的努力 (X
...上述第二種直接 import SVG 圖片的方式實際上是取得該圖檔的路徑,但在 CodeSandbox 中該圖檔路徑會是錯誤的,僅而有破圖的情況...
好像不是路徑錯誤的問題,因為改用PNG格式的圖片就不會破圖了。
謝謝,這個部分我還不太確定實際上在 codesandbox 上會導致破圖的原因,若你有發現的話再歡迎和我分享!
請問 我會遇到svg檔裡面的css顏色被覆蓋問題,後面載入的svg圖檔會有相同的css clsss名稱,導致畫面顏色出錯
你好,請問方便提供 codesandbox 的程式碼嗎?比較能夠知道情況。
發現真的要把svg檔裡面的class名稱改掉,才可以正確地呈現
哇!沒想到竟然有這種眉角!
看到你的作品了~哈哈,內容中如果還有發現什麼地方有誤或不夠嚴謹的話再麻煩跟我說。