动态

详情 返回 返回

CSS 實例系列 - 02 - 2023 兔年祝福 - 动态 详情

Hello 小夥伴們早上、中午、下午、晚上和深夜好,這裏是 jsliang~

新年新氣象,讓我們耍一個兔飛猛進的祝福吧:

02-01.gif

這個是一個完整的線上小實例,小夥伴們可以填寫數據,服務器會用 Node.js 定期讀取數據:

  • 填寫數據:https://kdocs.cn/l/cbmawranzvNL
  • 效果查看:https://liangjunrong.github.io/
例如你填的用户名稱是:abab,那麼你的鏈接就是:https://liangjunrong.github.io?username=abab

本期將和小夥伴們探討:

  • [x] 如何通過 HTML + 海量 CSS + 簡單 JS,完成這個兔年祝福實例
  • [x] 如何通過 Node.js,開啓無頭瀏覽器讀取「金山文檔」的數據,同步到 GitHub Page 上

本實例的代碼地址:

  • Demo —— all for one
  • 碼上掘金 - 02 - 2023 兔年祝福

一 前言

本 CSS 系列文章:

  1. 主推學以致用。結合面試題和工作實例,讓小夥伴們深入體驗 61 個工作常見的 CSS 屬性和各種 CSS 知識。
  2. 主推純 CSS。儘可能使用 HTML + CSS 完成學習目的,但仍然有 “一小部分” 功能需要用到 JavaScript 知識,適合新人學習 + 大佬複習

如果文章在一些細節上沒寫清楚或者誤導讀者,歡迎評論/吐槽/批判,你的點贊、收藏和關注是我更新的動力 ❤

  • 更多知識分享文章可見:jsliang 的文檔庫

二 前端實現

本實例的一些創意,參考自 Jamie Juviler 提供的 24 個 CSS 動畫,從中得到啓發創作了這封信,在此表示非常感謝:

  • 24 Creative and Unique CSS Animation Examples to Inspire Your Own

參考效果

  • 鼠標 hover 文本效果:《CSS Mouse Hover Transition Effect》

02-02.gif

  • 三個點:《Three Dots Loading》

02-03.gif

  • 信封:《Opening Envelope》

02-04.gif

OK,那麼咱們對着文件劃分以及最終渲染實例,「簡明扼要」講講界面是如何實現的:

- 02 - 2023 兔飛猛進
  - css                   —— 樣式表
    - heart.css           —— 心臟樣式
    - index.css           —— 主要樣式
    - letter-content.css  —— 信封樣式
    - letter-image.css    —— 信件樣式
    - tips.css            —— 下方提示樣式
  - js                    —— JS
    - index.js            —— 主要引用 JS
index.html                —— 首頁 HTML

02-01.gif

2.1 跳動的心臟

如何通過一個簡單的 <div> 實現跳動的心臟?

<div class="heart"></div>

其實很簡單:

02-05.png

  1. 先實現一個 200*200 大小的正方形,並旋轉 45°
  2. 通過 ::before,實現一個 100*200 的矩形,並通過 border-radius 設置圓角,做成左邊的半圓,最後通過 position 定位
  3. 通過 ::after,實現一個 200*300 的矩形(反過來),並通過 border-radius 設置圓角,做成右邊的長方形半圓,最後通過 position 定位
  4. 添加動畫,讓它循環跳動起來
.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;
}

這樣,中間的心就實現啦:

02-06.gif

2.2 滑動的文字

那麼,底部的含滑動效果的文字如何實現呢?

02-07.gif

其實也不難:

<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 其他

其他效果就不一一介紹了。

感興趣的小夥伴可自行前往代碼倉庫查看效果喔:

  • Demo —— all for one
  • 碼上掘金 - 02 - 2023 兔年祝福

三 服務端實現

OK,那麼界面實現後,我們如何讓數據「動」起來呢?

  1. 通過 data.json 存儲數據
  2. 通過 index.js,讀取 url 參數,並匹配 json 上的數據
  3. 將數據渲染到界面

02-08.png

這樣,我們是不是就能夠動態更換數據了?

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 推薦小夥伴看之前的文章:

  • jsliang - Node 工具庫

下面是逐步搭建步驟,詳細的解釋可以在上面工具庫系列文章查看,這裏就不一一介紹啦:

  • [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 目錄中:

02-09.png

  • [x] 安裝 Puppeteer:pnpm i puppeteer
當前(2023-01-15)最新版是 19.5.2,但是執行會報錯,需要指定版本。參考文獻:https://github.com/berstend/p...

通過 Node.js + Puppeteer 下載數據分 10 個小步驟:

  1. 啓動無頭瀏覽器
  2. 操作瀏覽器打開 https://kdocs.cn/l/cbmawranzvNL
  3. 睡眠 6.66s(確保瀏覽器打開鏈接並加載頁面)
  4. 如果有遮罩彈窗,需要觸發【x】按鈕關閉掉
  5. 觸發【更多菜單】按鈕的點擊
  6. 睡眠 2s(確保更多菜單按鈕點擊到)
  7. 設置下載路徑(確保 Puppeteer 下載路徑,避免【另存為】彈窗後不好處理)
  8. 觸發【下載】按鈕的點擊
  9. 睡眠 6.66s(確保資源下載到)
  10. 關閉窗口
唯一要關注的點是第 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 個小步驟:

  1. buffer 形式導入數據
  2. 讀取有效的數據(前面幾行為説明數據,且後面需要判斷數據是否冗餘)
  3. 將數據以 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 個小步驟:

  1. 前往 GitHub Page 倉庫
  2. 執行修改命令
  3. 推送到線上倉庫
  4. 回退上一層(方便下一次執行的時候目錄層級一致)
// 步驟三:上傳代碼到 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
  • 掘金

個人聯繫方式存放在 Github 首頁,歡迎一起折騰~

爭取打造自己成為一個充滿探索欲,喜歡折騰,樂於擴展自己知識面的終身學習斜槓程序員。

jsliang 的文檔庫由 梁峻榮 採用 知識共享 署名-非商業性使用-相同方式共享 4.0 國際 許可協議 進行許可。<br/>基於 https://github.com/LiangJunrong/document-library 上的作品創作。<br/>本許可協議授權之外的使用權限可以從 https://creativecommons.org/licenses/by-nc-sa/2.5/cn/ 處獲得。
user avatar zaotalk 头像 aqiongbei 头像 thosefree 头像 longlong688 头像 inslog 头像 banana_god 头像 solvep 头像 lhsuo 头像 imba97 头像 woniuseo 头像 zhulongxu 头像 ccVue 头像
点赞 83 用户, 点赞了这篇动态!
点赞

Add a new 评论

Some HTML is okay.