当前位置:   article > 正文

CSS 实例系列 - 02 - 2023 兔年祝福

html新年祝福代码

Hello 小伙伴们早上、中午、下午、晚上和深夜好,这里是 jsliang~

新年新气象,让我们耍一个兔飞猛进的祝福吧:

b064b61fcec54c527640512fd2cca215.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》

00f3ebe935a8eb32168f2f2ccd701984.gif

  • 三个点:《Three Dots Loading》

afbc2f3b5ead7c27d2118dbf557bca01.gif

  • 信封:《Opening Envelope》

8eb2564e5ba1b81bd454cafaa4945517.gif

OK,那么咱们对着文件划分以及最终渲染实例,「简明扼要」讲讲界面是如何实现的:

  1. 02 - 2023 兔飞猛进
  2.   - css                   —— 样式表
  3.     - heart.css           —— 心脏样式
  4.     - index.css           —— 主要样式
  5.     - letter-content.css  —— 信封样式
  6.     - letter-image.css    —— 信件样式
  7.     - tips.css            —— 下方提示样式
  8.   - js                    —— JS
  9.     - index.js            —— 主要引用 JS
  10. index.html                —— 首页 HTML

98753f7de503a0dd02dc3f5827df3266.gif

2.1 跳动的心脏

如何通过一个简单的 <div> 实现跳动的心脏?

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

其实很简单:

1f15127136203e191918b15736b4d9cf.png

  1. 先实现一个 200*200 大小的正方形,并旋转 45°

  2. 通过 ::before,实现一个 100*200 的矩形,并通过 border-radius 设置圆角,做成左边的半圆,最后通过 position 定位

  3. 通过 ::after,实现一个 200*300 的矩形(反过来),并通过 border-radius 设置圆角,做成右边的长方形半圆,最后通过 position 定位

  4. 添加动画,让它循环跳动起来

  1. .heart {
  2.   position: relative;
  3.   width: 200px;
  4.   height: 200px;
  5.   background: deeppink;
  6.   transform: rotate(45deg);
  7.   /* 关键动画:让心跳动起来 */
  8.   /* animation: 动画名称 | 动画时间 | 动画是否反向播放 | 动画运行的次数 */
  9.   animation: heartjump 0.5s alternate infinite;
  10. }
  11. @keyframes heartjump {
  12.   0% {
  13.     transform: rotate(45deg) scale(0.5);
  14.   }
  15.   100% {
  16.     transform: rotate(45deg) scale(1);
  17.   }
  18. }
  19. .heart::before, .heart::after {
  20.   position: absolute;
  21.   content: '';
  22.   background: deeppink;
  23. }
  24. .heart::before {
  25.   left: -99px;
  26.   width: 100px;
  27.   height: 200px;
  28.   border-radius: 100px 0 0 100px;
  29. }
  30. .heart::after {
  31.   top: -99px;
  32.   width: 200px;
  33.   height: 300px;
  34.   border-radius: 100px 100px 0 0;
  35.   /* 关键阴影:让爱心有立体感 */
  36.   /* box-shadow: x 偏移量 | y 轴偏移量 | 阴影模糊半径 | 阴影扩散半径 | 阴影颜色 */
  37.   box-shadow: 10px -5px 10px 0 #ccc;
  38. }

这样,中间的心就实现啦:

368a02b4293e12fdcb2d2fa4af73c4d7.gif

2.2 滑动的文字

那么,底部的含滑动效果的文字如何实现呢?

741230a79d4056c65f109b775222aad8.gif

其实也不难:

  1. <p class="tips">
  2.   <!-- 提示文本 -->
  3.   <span class="tips-info">
  4.     <!-- TODO: 提示 - 用户填充 -->
  5.   </span>
  6.   <!-- 三个点 -->
  7.   <span class="three-dots">
  8.     <span class="three-dots-element"></span>
  9.     <span class="three-dots-element"></span>
  10.     <span class="three-dots-element"></span>
  11.   </span>
  12. </p>

这里看关键 CSS 的实现:

  1. .tips {
  2.   margin-top: 50px;
  3.   position: relative;
  4.   padding: 8px;
  5.   border-radius: 8px;
  6.   border: 1px solid deepskyblue;
  7.   color: #000;
  8.   cursor: pointer;
  9.   /* 关键动画:颜色的改变 */
  10.   transition: color .3s;
  11. }
  12. .tips:hover {
  13.   color: #fff;
  14. }
  15. .tips:hover::before {
  16.   /* 关键动画 - 从左下开始 */
  17.   transform: scaleX(1);
  18.   transform-origin: bottom left;
  19. }
  20. .tips::before {
  21.   content: ' ';
  22.   display: block;
  23.   position: absolute;
  24.   /* https://developer.mozilla.org/en-US/docs/Web/CSS/inset */
  25.   inset: 0 0 0 0;
  26.   background: deepskyblue;
  27.   border-radius: 8px;
  28.   z-index: -1;
  29.   
  30.   /* 关键动画 - 从右下开始 */
  31.   transition: transform 1s ease;
  32.   transform: scaleX(0);
  33.   transform-origin: bottom right;
  34. }

看完是不是豁然开朗:

  • 原来只要通过 ::before 设置好蓝色背景,然后添加 transition,让它从左往右「跑」起来

2.3 其他

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

感兴趣的小伙伴可自行前往代码仓库查看效果喔:

  • Demo —— all for one

  • 码上掘金 - 02 - 2023 兔年祝福

三 服务端实现

OK,那么界面实现后,我们如何让数据「动」起来呢?

  1. 通过 data.json 存储数据

  2. 通过 index.js,读取 url 参数,并匹配 json 上的数据

  3. 将数据渲染到界面

d202cb181d838b587429f8ac215b5e6a.png

这样,我们是不是就能够动态更换数据了?

3.1 前后端数据对接

假设我们有个 json 文件来存储数据:

data.json

  1. [
  2.   {
  3.     "username""jsliang",
  4.     "tipsInfo""Hello 小伙伴们,点击 ❤ 查看我给你们的信",
  5.     "letterContentTitle""给 2022 的你们",
  6.     "letterContentMain""☆ 2022 随风飘逝,2023,我们来啦!\n☆ 在新春佳节到来之际,祝您全家身体健康,万事如意!\n☆ 兔年的祝福短信飞雪迎春到,玉兔捧福来。\n☆ 除夕的钟声扣响你快乐的心扉,新年礼炮奏响你幸福华章,缤纷焰火编织你闪亮生活,八仙给力保你万事胜意。\n☆ 祝您一帆风顺,四季平安,八方进财!\n☆ 兔年祝愿天下朋友:工作舒心,薪水合心,被窝暖心,朋友知心,爱人同心,一切都顺心,永远都开心,事事都称心!\n",
  7.     "letterContentButton""加油 2023!"
  8.   }
  9. ]

index.js 上进行读取,看用户输入了什么:

  1. // 获取节点
  2. const tipsInfo = document.querySelector('.tips-info');
  3. const letterContentTitle = document.querySelector('.letter-content-title');
  4. const letterContentMain = document.querySelector('.letter-content-main');
  5. const letterContentButton = document.querySelector('.letter-content-button');
  6. // 获取 URL 参数
  7. let query;
  8. const getQuery = (info) => {
  9.   if (query) {
  10.     return query.get(info);
  11.   }
  12.   query = new URLSearchParams(window.location.search);
  13.   return query.get(info);
  14. };
  15. // 读取 JSON 数据
  16. const data = await fetch('./data.json');
  17. const userinfo = await data.json();
  18. console.log('data: ', userinfo);
  19. const username = getQuery('username') || 'jsliang';
  20. // 匹配并渲染数据
  21. for (let i = 0; i < userinfo.length; i++) {
  22.   const item = userinfo[i];
  23.   if (item.username === username) {
  24.     tipsInfo.innerText = item.tipsInfo;
  25.     letterContentTitle.innerText = item.letterContentTitle;
  26.     letterContentMain.innerText = item.letterContentMain;
  27.     letterContentButton.innerText = item.letterContentButton;
  28.     break;
  29.   }
  30. }

这样,我们基础数据构思就完成了!

接下来只需要通过 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 服务就搭起来了。

它的目录结构如下:

  1. 02 - 2023 兔年祝福
  2.   - LiangJunrong.github.io  —— GitHub Page 仓库
  3.   - src                     —— 项目主代码
  4.     - dist                  —— Excel 下载地址
  5.     - index.ts              —— 主入口
  6.   - .gitignore              —— Git 忽略配置
  7.   - package.json            —— npm 包管理
  8.   - pnpm-lock.yaml          —— npm 包管理
  9.   - tsconfig.json           —— TSLint

3.3 下载数据

下面我们开始下载数据,这里的目标是将数据下载到 src/dist 目录中:

07ee1c46c0f67e610f93c9dc31717c26.png

  • [x] 安装 Puppeteer:pnpm i puppeteer

当前(2023-01-15)最新版是 19.5.2,但是执行会报错,需要指定版本。参考文献:https://github.com/berstend/puppeteer-extra/issues/651

通过 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 点击下载是会有弹窗的(并不是默认下载)

它的实现代码如下:

  1. // 步骤一:下载 Excel
  2. const downloadExcel = async() => {
  3.   // 1. 启动无头浏览器
  4.   const browser = await puppeteer.launch({
  5.     // 是否打开实体浏览器
  6.     headless: false,
  7.     // 打开开发模式
  8.     devtools: true,
  9.   });
  10.   // 2. 操作浏览器打开 `https://kdocs.cn/l/cbmawranzvNL`
  11.   const page = await browser.newPage();
  12.   await page.goto('https://kdocs.cn/l/cbmawranzvNL');
  13.   // 3. 睡眠 6.66s(确保浏览器打开链接并加载页面)
  14.   await page.waitForTimeout(6666);
  15.   // 4. 如果有遮罩弹窗,需要触发【x】按钮关闭掉
  16.   const closeBtn = await page.$('.modal-wrap .icons-16-close');
  17.   closeBtn?.click();
  18.   // 5. 触发【更多菜单】按钮的点击
  19.   const moreBtn = await page.$('.header-more-btn');
  20.   moreBtn?.click();
  21.   // 6. 睡眠 2s(确保更多菜单按钮点击到)
  22.   await page.waitForTimeout(2000);
  23.   // 7. 设置下载路径(确保 Puppeteer 下载路径,避免【另存为】弹窗后不好处理)
  24.   const dist = path.join(__dirname, './dist');
  25.   if (!fs.existsSync(dist)) {
  26.     fs.mkdirSync(dist);
  27.   }
  28.   // 如果报错,请修改 Puppeteer 为 14.3.0:https://github.com/berstend/puppeteer-extra/issues/651
  29.   await (page as any)._client?.send('Page.setDownloadBehavior', {
  30.     behavior: 'allow',
  31.     downloadPath: dist,
  32.   });
  33.   // 8. 触发【下载】按钮的点击
  34.   // @ts-ignore
  35.   const downloadBtn = await page.$('div[data-key=Download]');
  36.   downloadBtn?.click();
  37.   // 9. 睡眠 6.66s(确保资源下载到)
  38.   await page.waitForTimeout(6666);
  39.   // 10. 关闭窗口
  40.   await browser.close();
  41. }

3.4 读取数据

接着,我们需要通过 node-xlsx 来读取下载后的数据:

  • 安装 Excel 读取模块:pnpm i node-xlsx -S + pnpm i @types/node-xlsx -D

它分为 3 个小步骤:

  1. buffer 形式导入数据

  2. 读取有效的数据(前面几行为说明数据,且后面需要判断数据是否冗余)

  3. 将数据以 JSON 的形式存储到 GitHub Page 仓库

  1. // 步骤二:读取 Excel 并存储 JSON 数据
  2. const readExcel = async() => {
  3.   // 1. 以 buffer 形式导入数据
  4.   const workSheetsFromBuffer = xlsx.parse(fs.readFileSync(`${__dirname}/dist/「新春贺词 - 兔飞猛进」.xlsx`));
  5.   // 含图片等数据的时候,第 1 条才是文本数据
  6.   const stringifyData: any = JSON.parse(JSON.stringify(workSheetsFromBuffer, null, 2));
  7.   const data = stringifyData[0]?.data;
  8.   // 2. 读取有效的数据(前面几行为说明数据,且后面需要判断数据是否冗余)
  9.   const result = [];
  10.   for (let i = 3; i < data.length; i++) {
  11.     const item = data[i];
  12.     const [
  13.       username,
  14.       tipsInfo = '点击 ❤ 查看我给你的信',
  15.       letterContentTitle = 'A Letter for you',
  16.       letterContentMain,
  17.       letterContentButton = 'Love ❤ you',
  18.     ] = item;
  19.     // 如果没数据了,则不填写
  20.     if (!username || !letterContentMain) {
  21.       continue;
  22.     }
  23.     result.push({
  24.       username,
  25.       tipsInfo,
  26.       letterContentTitle,
  27.       letterContentMain,
  28.       letterContentButton,
  29.     });
  30.   }
  31.   // 3. 将数据以 JSON 的形式存储到 GitHub Page 仓库
  32.   const GPCatalog = path.join(process.cwd(), './LiangJunrong.github.io/data.json');
  33.   fs.writeFileSync(GPCatalog, JSON.stringify(result));
  34. };

3.5 上传代码

接下来只需要将代码上传到 GitHub Page 即可:

  • 安装 shell 模块:pnpm i shelljs + pnpm i @types/shelljs -D

它分 3 个小步骤:

  1. 前往 GitHub Page 仓库

  2. 执行修改命令

  3. 推送到线上仓库

  4. 回退上一层(方便下一次执行的时候目录层级一致)

  1. // 步骤三:上传代码到 GitHub Page
  2. const uploadCode = async() => {
  3.   // 1. 前往 GitHub Page 仓库
  4.   await shell.cd(`LiangJunrong.github.io`);
  5.   // 2. 执行修改命令
  6.   await shell.exec(`git add .`);
  7.   await shell.exec(`git commit -m "fix: 更新线上数据"`);
  8.   // 3. 推送到线上仓库
  9.   await shell.exec('git push');
  10.   // 4. 回退上一层(方便下一次执行的时候目录层级一致)
  11.   await shell.cd(`../`);
  12. };

3.6 设置定时任务

最后,我们只需要设置电脑定时任务,让它可以定时读取线上数据并上传就好啦!

  • 安装 node-schedule 模块:pnpm i node-schedule + pnpm i @types/node-schedule -D

只需要简单一步代码:

  1. import puppeteer from 'puppeteer';
  2. import path from 'path';
  3. import fs from 'fs';
  4. import xlsx from 'node-xlsx';
  5. import shell from 'shelljs';
  6. import schedule from 'node-schedule';
  7. // 步骤四:设置定时器,定时上传代码
  8. const runCode = async() => {
  9.   console.log('Hello 2023~');
  10.   // scheduleJob: 秒 分 时 日 月 周几
  11.   schedule.scheduleJob('*/10 * * * *', async() => {
  12.     console.log('开始操作');
  13.     // 步骤一:下载 Excel
  14.     await downloadExcel();
  15.     // 步骤二:读取 Excel 并存储 JSON 数据
  16.     await readExcel();
  17.     // 步骤三:上传代码到 GitHub Page
  18.     await uploadCode();
  19.   });
  20. };
  21. const program = require('commander');
  22. program
  23.   .version('1.0.0')
  24.   .description('2023 兔年祝福')
  25.   .command('2023')
  26.   .action(async() => {
  27.     // 步骤四:设置定时器,定时上传代码
  28.     await runCode();
  29.   });
  30. 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 国际 许可协议 进行许可。
基于 https://github.com/LiangJunrong/document-library 上的作品创作。
本许可协议授权之外的使用权限可以从 https://creativecommons.org/licenses/by-nc-sa/2.5/cn/ 处获得。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/96393?site
推荐阅读
相关标签
  

闽ICP备14008679号