Hello 小夥伴們早上、中午、下午、晚上和深夜好,這裡是 jsliang~
新年新氣象,讓我們耍一個兔飛猛進的祝福吧:
這個是一個完整的線上小例項,小夥伴們可以填寫資料,伺服器會用 Node.js 定期讀取資料:
例如你填的使用者名稱稱是:abab
,那麼你的連結就是:https://liangjunrong.github.io?username=abab
本期將和小夥伴們探討:
- [x] 如何透過 HTML + 海量 CSS + 簡單 JS,完成這個兔年祝福例項
- [x] 如何透過 Node.js,開啟無頭瀏覽器讀取「金山文件」的資料,同步到 GitHub Page 上
本例項的程式碼地址:
一 前言
本 CSS 系列文章:
- 主推學以致用。結合面試題和工作例項,讓小夥伴們深入體驗 61 個工作常見的 CSS 屬性和各種 CSS 知識。
- 主推純 CSS。儘可能使用 HTML + CSS 完成學習目的,但仍然有 “一小部分” 功能需要用到 JavaScript 知識,適合新人學習 + 大佬複習。
如果文章在一些細節上沒寫清楚或者誤導讀者,歡迎評論/吐槽/批判,你的點贊、收藏和關注是我更新的動力 ❤
- 更多知識分享文章可見:jsliang 的文件庫
二 前端實現
本例項的一些創意,參考自 Jamie Juviler 提供的 24 個 CSS 動畫,從中得到啟發創作了這封信,在此表示非常感謝:
參考效果:
- 滑鼠 hover 文字效果:《CSS Mouse Hover Transition Effect》
- 三個點:《Three Dots Loading》
- 信封:《Opening Envelope》
OK,那麼我們們對著檔案劃分以及最終渲染例項,「簡明扼要」講講介面是如何實現的:
- 02 - 2023 兔飛猛進
- css —— 樣式表
- heart.css —— 心臟樣式
- index.css —— 主要樣式
- letter-content.css —— 信封樣式
- letter-image.css —— 信件樣式
- tips.css —— 下方提示樣式
- js —— JS
- index.js —— 主要引用 JS
index.html —— 首頁 HTML
2.1 跳動的心臟
如何透過一個簡單的 <div>
實現跳動的心臟?
<div class="heart"></div>
其實很簡單:
- 先實現一個 200*200 大小的正方形,並旋轉 45°
- 透過
::before
,實現一個 100*200 的矩形,並透過border-radius
設定圓角,做成左邊的半圓,最後透過position
定位 - 透過
::after
,實現一個 200*300 的矩形(反過來),並透過border-radius
設定圓角,做成右邊的長方形半圓,最後透過position
定位 - 新增動畫,讓它迴圈跳動起來
.heart {
position: relative;
width: 200px;
height: 200px;
background: deeppink;
transform: rotate(45deg);
/* 關鍵動畫:讓心跳動起來 */
/* animation: 動畫名稱 | 動畫時間 | 動畫是否反向播放 | 動畫執行的次數 */
animation: heartjump 0.5s alternate infinite;
}
@keyframes heartjump {
0% {
transform: rotate(45deg) scale(0.5);
}
100% {
transform: rotate(45deg) scale(1);
}
}
.heart::before, .heart::after {
position: absolute;
content: '';
background: deeppink;
}
.heart::before {
left: -99px;
width: 100px;
height: 200px;
border-radius: 100px 0 0 100px;
}
.heart::after {
top: -99px;
width: 200px;
height: 300px;
border-radius: 100px 100px 0 0;
/* 關鍵陰影:讓愛心有立體感 */
/* box-shadow: x 偏移量 | y 軸偏移量 | 陰影模糊半徑 | 陰影擴散半徑 | 陰影顏色 */
box-shadow: 10px -5px 10px 0 #ccc;
}
這樣,中間的心就實現啦:
2.2 滑動的文字
那麼,底部的含滑動效果的文字如何實現呢?
其實也不難:
<p class="tips">
<!-- 提示文字 -->
<span class="tips-info">
<!-- TODO: 提示 - 使用者填充 -->
</span>
<!-- 三個點 -->
<span class="three-dots">
<span class="three-dots-element"></span>
<span class="three-dots-element"></span>
<span class="three-dots-element"></span>
</span>
</p>
這裡看關鍵 CSS 的實現:
.tips {
margin-top: 50px;
position: relative;
padding: 8px;
border-radius: 8px;
border: 1px solid deepskyblue;
color: #000;
cursor: pointer;
/* 關鍵動畫:顏色的改變 */
transition: color .3s;
}
.tips:hover {
color: #fff;
}
.tips:hover::before {
/* 關鍵動畫 - 從左下開始 */
transform: scaleX(1);
transform-origin: bottom left;
}
.tips::before {
content: ' ';
display: block;
position: absolute;
/* https://developer.mozilla.org/en-US/docs/Web/CSS/inset */
inset: 0 0 0 0;
background: deepskyblue;
border-radius: 8px;
z-index: -1;
/* 關鍵動畫 - 從右下開始 */
transition: transform 1s ease;
transform: scaleX(0);
transform-origin: bottom right;
}
看完是不是豁然開朗:
- 原來只要透過
::before
設定好藍色背景,然後新增transition
,讓它從左往右「跑」起來
2.3 其他
其他效果就不一一介紹了。
感興趣的小夥伴可自行前往程式碼倉庫檢視效果喔:
三 服務端實現
OK,那麼介面實現後,我們如何讓資料「動」起來呢?
- 透過
data.json
儲存資料 - 透過
index.js
,讀取url
引數,並匹配json
上的資料 - 將資料渲染到介面
這樣,我們是不是就能夠動態更換資料了?
3.1 前後端資料對接
假設我們有個 json
檔案來儲存資料:
data.json
[
{
"username": "jsliang",
"tipsInfo": "Hello 小夥伴們,點選 ❤ 檢視我給你們的信",
"letterContentTitle": "給 2022 的你們",
"letterContentMain": "☆ 2022 隨風飄逝,2023,我們來啦!\n☆ 在新春佳節到來之際,祝您全家身體健康,萬事如意!\n☆ 兔年的祝福簡訊飛雪迎春到,玉兔捧福來。\n☆ 除夕的鐘聲扣響你快樂的心扉,新年禮炮奏響你幸福華章,繽紛焰火編織你閃亮生活,八仙給力保你萬事勝意。\n☆ 祝您一帆風順,四季平安,八方進財!\n☆ 兔年祝願天下朋友:工作舒心,薪水合心,被窩暖心,朋友知心,愛人同心,一切都順心,永遠都開心,事事都稱心!\n",
"letterContentButton": "加油 2023!"
}
]
在 index.js
上進行讀取,看使用者輸入了什麼:
// 獲取節點
const tipsInfo = document.querySelector('.tips-info');
const letterContentTitle = document.querySelector('.letter-content-title');
const letterContentMain = document.querySelector('.letter-content-main');
const letterContentButton = document.querySelector('.letter-content-button');
// 獲取 URL 引數
let query;
const getQuery = (info) => {
if (query) {
return query.get(info);
}
query = new URLSearchParams(window.location.search);
return query.get(info);
};
// 讀取 JSON 資料
const data = await fetch('./data.json');
const userinfo = await data.json();
console.log('data: ', userinfo);
const username = getQuery('username') || 'jsliang';
// 匹配並渲染資料
for (let i = 0; i < userinfo.length; i++) {
const item = userinfo[i];
if (item.username === username) {
tipsInfo.innerText = item.tipsInfo;
letterContentTitle.innerText = item.letterContentTitle;
letterContentMain.innerText = item.letterContentMain;
letterContentButton.innerText = item.letterContentButton;
break;
}
}
這樣,我們基礎資料構思就完成了!
接下來只需要透過 Node.js,將資料填充到 data.json
即可,簡簡單單~
3.2 Node.js 服務搭建
OK,接下來我們需要考慮的是,從哪裡 白嫖 做免費的資料儲存,並且能夠抓下來。
這次我們考慮的是使用「金山文件」的線上「表格」,因為它不僅可以滿足 使用者共享填寫資料,並且方便我們 透過無頭瀏覽器抓取資料。
這裡就需要利用 Node.js + Puppeteer 來下載資料了。
當然,前置知識點是存在的,但是篇幅有限,「孩子沒娘,說來話長」,jsliang 推薦小夥伴看之前的文章:
下面是逐步搭建步驟,詳細的解釋可以在上面工具庫系列文章檢視,這裡就不一一介紹啦:
- [x] 下載安裝 Node.js
- [x] 下載安裝 Visio Studio Code
- [x] 初始化倉庫:
npm init --yes
- [x] 安裝初始化包:
pnpm i @types/node typescript ts-node -D
- [x] 初始化 TypeScript 配置:
tsc --init
- [x] 安裝 ESLint:
pnpm i eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin -D
- [x] 安裝 Commander:
pnpm i commander@14.3.0
- [x] 修改 package.json,可執行
npm run 2023
OK,到這一步,基礎的 Node.js 服務就搭起來了。
它的目錄結構如下:
- 02 - 2023 兔年祝福
- LiangJunrong.github.io —— GitHub Page 倉庫
- src —— 專案主程式碼
- dist —— Excel 下載地址
- index.ts —— 主入口
- .gitignore —— Git 忽略配置
- package.json —— npm 包管理
- pnpm-lock.yaml —— npm 包管理
- tsconfig.json —— TSLint
3.3 下載資料
下面我們開始下載資料,這裡的目標是將資料下載到 src/dist
目錄中:
- [x] 安裝 Puppeteer:
pnpm i puppeteer
當前(2023-01-15)最新版是 19.5.2,但是執行會報錯,需要指定版本。參考文獻:https://github.com/berstend/p...
透過 Node.js + Puppeteer 下載資料分 10 個小步驟:
- 啟動無頭瀏覽器
- 操作瀏覽器開啟
https://kdocs.cn/l/cbmawranzvNL
- 睡眠 6.66s(確保瀏覽器開啟連結並載入頁面)
- 如果有遮罩彈窗,需要觸發【x】按鈕關閉掉
- 觸發【更多選單】按鈕的點選
- 睡眠 2s(確保更多選單按鈕點選到)
- 設定下載路徑(確保 Puppeteer 下載路徑,避免【另存為】彈窗後不好處理)
- 觸發【下載】按鈕的點選
- 睡眠 6.66s(確保資源下載到)
- 關閉視窗
唯一要關注的點是第 5 點,因為我們 Windows 點選下載是會有彈窗的(並不是預設下載)
它的實現程式碼如下:
// 步驟一:下載 Excel
const downloadExcel = async() => {
// 1. 啟動無頭瀏覽器
const browser = await puppeteer.launch({
// 是否開啟實體瀏覽器
headless: false,
// 開啟開發模式
devtools: true,
});
// 2. 操作瀏覽器開啟 `https://kdocs.cn/l/cbmawranzvNL`
const page = await browser.newPage();
await page.goto('https://kdocs.cn/l/cbmawranzvNL');
// 3. 睡眠 6.66s(確保瀏覽器開啟連結並載入頁面)
await page.waitForTimeout(6666);
// 4. 如果有遮罩彈窗,需要觸發【x】按鈕關閉掉
const closeBtn = await page.$('.modal-wrap .icons-16-close');
closeBtn?.click();
// 5. 觸發【更多選單】按鈕的點選
const moreBtn = await page.$('.header-more-btn');
moreBtn?.click();
// 6. 睡眠 2s(確保更多選單按鈕點選到)
await page.waitForTimeout(2000);
// 7. 設定下載路徑(確保 Puppeteer 下載路徑,避免【另存為】彈窗後不好處理)
const dist = path.join(__dirname, './dist');
if (!fs.existsSync(dist)) {
fs.mkdirSync(dist);
}
// 如果報錯,請修改 Puppeteer 為 14.3.0:https://github.com/berstend/puppeteer-extra/issues/651
await (page as any)._client?.send('Page.setDownloadBehavior', {
behavior: 'allow',
downloadPath: dist,
});
// 8. 觸發【下載】按鈕的點選
// @ts-ignore
const downloadBtn = await page.$('div[data-key=Download]');
downloadBtn?.click();
// 9. 睡眠 6.66s(確保資源下載到)
await page.waitForTimeout(6666);
// 10. 關閉視窗
await browser.close();
}
3.4 讀取資料
接著,我們需要透過 node-xlsx
來讀取下載後的資料:
- 安裝 Excel 讀取模組:
pnpm i node-xlsx -S
+pnpm i @types/node-xlsx -D
它分為 3 個小步驟:
- 以
buffer
形式匯入資料 - 讀取有效的資料(前面幾行為說明資料,且後面需要判斷資料是否冗餘)
- 將資料以
JSON
的形式儲存到 GitHub Page 倉庫
// 步驟二:讀取 Excel 並儲存 JSON 資料
const readExcel = async() => {
// 1. 以 buffer 形式匯入資料
const workSheetsFromBuffer = xlsx.parse(fs.readFileSync(`${__dirname}/dist/「新春賀詞 - 兔飛猛進」.xlsx`));
// 含圖片等資料的時候,第 1 條才是文字資料
const stringifyData: any = JSON.parse(JSON.stringify(workSheetsFromBuffer, null, 2));
const data = stringifyData[0]?.data;
// 2. 讀取有效的資料(前面幾行為說明資料,且後面需要判斷資料是否冗餘)
const result = [];
for (let i = 3; i < data.length; i++) {
const item = data[i];
const [
username,
tipsInfo = '點選 ❤ 檢視我給你的信',
letterContentTitle = 'A Letter for you',
letterContentMain,
letterContentButton = 'Love ❤ you',
] = item;
// 如果沒資料了,則不填寫
if (!username || !letterContentMain) {
continue;
}
result.push({
username,
tipsInfo,
letterContentTitle,
letterContentMain,
letterContentButton,
});
}
// 3. 將資料以 JSON 的形式儲存到 GitHub Page 倉庫
const GPCatalog = path.join(process.cwd(), './LiangJunrong.github.io/data.json');
fs.writeFileSync(GPCatalog, JSON.stringify(result));
};
3.5 上傳程式碼
接下來只需要將程式碼上傳到 GitHub Page 即可:
- 安裝 shell 模組:
pnpm i shelljs
+pnpm i @types/shelljs -D
它分 3 個小步驟:
- 前往 GitHub Page 倉庫
- 執行修改命令
- 推送到線上倉庫
- 回退上一層(方便下一次執行的時候目錄層級一致)
// 步驟三:上傳程式碼到 GitHub Page
const uploadCode = async() => {
// 1. 前往 GitHub Page 倉庫
await shell.cd(`LiangJunrong.github.io`);
// 2. 執行修改命令
await shell.exec(`git add .`);
await shell.exec(`git commit -m "fix: 更新線上資料"`);
// 3. 推送到線上倉庫
await shell.exec('git push');
// 4. 回退上一層(方便下一次執行的時候目錄層級一致)
await shell.cd(`../`);
};
3.6 設定定時任務
最後,我們只需要設定電腦定時任務,讓它可以定時讀取線上資料並上傳就好啦!
- 安裝 node-schedule 模組:
pnpm i node-schedule
+pnpm i @types/node-schedule -D
只需要簡單一步程式碼:
import puppeteer from 'puppeteer';
import path from 'path';
import fs from 'fs';
import xlsx from 'node-xlsx';
import shell from 'shelljs';
import schedule from 'node-schedule';
// 步驟四:設定定時器,定時上傳程式碼
const runCode = async() => {
console.log('Hello 2023~');
// scheduleJob: 秒 分 時 日 月 周幾
schedule.scheduleJob('*/10 * * * *', async() => {
console.log('開始操作');
// 步驟一:下載 Excel
await downloadExcel();
// 步驟二:讀取 Excel 並儲存 JSON 資料
await readExcel();
// 步驟三:上傳程式碼到 GitHub Page
await uploadCode();
});
};
const program = require('commander');
program
.version('1.0.0')
.description('2023 兔年祝福')
.command('2023')
.action(async() => {
// 步驟四:設定定時器,定時上傳程式碼
await runCode();
});
program.parse(process.argv);
3.7 小結
透過上面操作,我們可以看到:
- 透過
node-schedule
定期執行任務 - 透過
puppeteer
下載線上資料 - 透過
node-xlsx
讀取下載下來的 Excel 檔案 - 透過
shell
操作 Shell,上傳資料 - 透過 JavaScript 讀取 JSON 檔案,將資料渲染到 HTML
這樣,我們就完成了本次的祝福例項!
OK,完事,收工~
四 參考文獻
- 24 Creative and Unique CSS Animation Examples to Inspire Your Own
- 掘金 - KevinQ - 純CSS製作跳動的心
- 部落格園 - whys - CSS 畫一個心
不折騰的前端,和鹹魚有什麼區別!
覺得文章不錯的小夥伴歡迎點贊/點 Star。
如果小夥伴需要聯絡 jsliang:
個人聯絡方式存放在 Github 首頁,歡迎一起折騰~
爭取打造自己成為一個充滿探索欲,喜歡折騰,樂於擴充套件自己知識面的終身學習斜槓程式設計師。
jsliang 的文件庫由 梁峻榮 採用 知識共享 署名-非商業性使用-相同方式共享 4.0 國際 許可協議 進行許可。<br/>基於 https://github.com/LiangJunrong/document-library 上的作品創作。<br/>本許可協議授權之外的使用許可權可以從 https://creativecommons.org/licenses/by-nc-sa/2.5/cn/ 處獲得。