TL;DR
当青训营遇上码上掘金 | 会动的名片
作为一个前端工程师🦁️,这次选择的是 主题 1:我的名片,用代码来介绍自己,这太酷了。
话不多说,先看 👀 成品:
小屏不大好看,可以在 code.juejin.cn 或者 self-introduction.icodeq.com 查看全屏幕版本
代码仓库:https://github.com/zkeq/Doraemon/tree/Self-introduction
项目思路
看到题目之后,原本是想要仿写一个闪光 📸️ 卡片的:poke-holo.simey.me
但是因为技术栈不同,其实是不知道怎么抄。
只能退而求其次,用我之前写过一个 会动的代码:哆啦A梦 作为模版
详情看这里,也是上次的掘金入围作者啦(只是过了初审而已hh):
🤔,大概就是这样
其实 js 部分很简单
| const cssString = `...这部分省略...`; | |
| let codeString = ''; | |
| const textDom = document.querySelector('.text'); | |
| const styleDom = document.querySelector('.doraemon-style'); | |
| const playButton = document.querySelector('.play'); | |
| const stopButton = document.querySelector('.stop'); | |
| const progressBar = document.querySelector('.progress-bar'); | |
| let textStartIndex = 0; | |
| let range = 3; | |
| let timer = null; | |
| const { width: finalWidth } = document.querySelector('body').getBoundingClientRect(); | |
| const updateProgress = (progress) => { | |
| progressBar.style.width = `${finalWidth * progress}px`; | |
| }; | |
| let init = 0; | |
| const len = cssString.length; | |
| const play = () => { | |
| if (textStartIndex >= len) { | |
| textStartIndex = 0; | |
| } | |
| if (timer) return; | |
| timer = setInterval(() => { | |
| textStartIndex += range; | |
| if (textStartIndex > len) { | |
| playButton.innerHTML = '重放' | |
| textDom.innerHTML = cssString.substring(0, len); | |
| styleDom.innerHTML = cssString.substring(0, len); | |
| updateProgress(1); | |
| // textDom.scrollTop = textDom.scrollHeight; | |
| hljs.highlightAll(); | |
| window.clearInterval(timer); | |
| timer = null; | |
| } else { | |
| playButton.innerHTML = '播放' | |
| textDom.innerHTML = cssString.substring(0, textStartIndex); | |
| styleDom.innerHTML = cssString.substring(0, textStartIndex); | |
| updateProgress(textStartIndex / len); | |
| textDom.scrollTop = textDom.scrollHeight; | |
| hljs.highlightAll(); | |
| } | |
| }, 0); | |
| }; | |
| const stop = () => { | |
| window.clearInterval(timer); | |
| auio.pause(); | |
| timer = null; | |
| }; | |
| playButton.onclick = play; | |
| stopButton.onclick = stop; |
一些技术细节?
Q1. 首先就是第一个点:如何实现左边代码右边实时效果一起显示呢?
A1: style 属性其实也可以有自己的 class,当我们选中它,并且使用 js 动态的向内添加内容时,页面的样式也会随之变化,那么我们只需要同时向两个元素中添加 css 字符即可,一个用于可视化,一个用于页面的真实渲染。
Q2. 代码块的高亮怎么实现的?
A2: 用 <pre> 标签包起来,然后: highlightjs.org
开始写吧!
那么有了大概的思路,我们就可以开始创作啦。
首先理了一下要放点什么东西来展示,大概就是以下这些:
| 1. 自己的博客,更新了230+篇博客,总PV 12W+; | |
| 2. Github,90+ followers,250+ stars; | |
| 3. 运维了一个由多服务杂糅到一起的资源站,累计UV50W+,累计PV 300W+; | |
| 4. 会Python,用Python写过很多没什么技术但是很好玩的东西,包括每日早报、每日Bing壁纸、上传接口、交作业网站、文件分享站、十几个小小API,用到了云函数; | |
| 5. 前端,今年6月才开始在饥人谷好好学,用Vue2.7+Element做出过好几个单页,没用框架的时候写过LearnOnly导航站、每日早报、追梦计划、自建不蒜子等。 |
然后就是找了一堆图,再把简单的框架搭起来。
| <div class="card_wrap"> | |
| <section class="section-0"> | |
| <h3>Hello, I am Zkeq。 👋</h3> | |
| </section> | |
| <section class="section-1"> | |
| <h3>#000 一个大一学生,学过 Python,目前在学前端。</h3> | |
| <h4>作为程序员,GitHub 的数据应该可以代表一些事情:</h4> | |
| <a href="https://github.com/zkeq" target="_blank"> | |
| <img src="https://stats.readme.icodeq.com/api?username=zkeq&show_icons=true&theme=radical&hide_border=true"> | |
| </a> | |
| <button class="btn-next" onclick="play()">继续播放</button> | |
| </section> | |
| <section class="section-2"> | |
| <h3>#00F 那么,我都做过什么呢?</h3> | |
| <h5>我想,最令我满意的,应该是:<a target="_blank" href="https://cdn.onmicrosoft.cn"> 渺软 CDN</a></h5> | |
| <a href="https://cdn.onmicrosoft.cn" target="_blank"> | |
| <img src="https://bu.dusays.com/2023/01/15/63c2d4143b8d7.png"> | |
| </a> | |
| <ul> | |
| <li>同时 这也是最近的项目 ( 12.31 上线 1.7 完成重构) </li> | |
| <li>主要是为了解决前端在开发简单 demo 时遇到的静态资源引入的问题。</li> | |
| <li>详情请移步评论区最后一条查看 更新日志</li> | |
| <li>页面重构其实是 @xcsoft 大佬帮忙做的,我也在努力学习做出漂亮页面啦。</li> | |
| </ul> | |
| <button class="btn-next" onclick="play()">继续播放</button> | |
| </section> | |
| <section class="section-3"> | |
| <h3>#0FF 再者重要的,应该就是我的博客啦。</h3> | |
| <a href="https://icodeq.com" target="_blank"> | |
| <img src="https://img.onmicrosoft.cn/2022-09-08/image-20220909153929835.png"> | |
| </a> | |
| <h5>到目前的话,应该是在很用心的运营这个博客的!</h5> | |
| <ul> | |
| <li>博文更新 240+ 篇,累计字数 146k+ 。</li> | |
| <li>虽然大部分都是学习的笔记吧,但是在这个过程中养成了整理知识点的好习惯!</li> | |
| <li>因为访问数据被清过一次,所以访问人数应该在 10w uv +</li> | |
| <li>因为这个博客折腾出了很多周边产品(x) </li> | |
| <li>感觉有这样一个可以自己掌控的小天地很舒服</li> | |
| </ul> | |
| <h5>这里是最近的博文哦:</h5> | |
| <a href="https://icodeq.com" target="_blank"> | |
| <img src="https://zkeq.xyz/Profile/article.svg"> | |
| </a> | |
| <ul> | |
| <li>因为是学习委员,再加上我们班的专业就是前端</li> | |
| <li>所以暑假带着他们一起学习啦。</li> | |
| </ul> | |
| <button class="btn-next" onclick="play()">继续播放</button> | |
| </section> | |
| <section class="section-4"> | |
| <h3>#0F0 优雅至极!追梦计划!</h3> | |
| <a href="https://dream-plan.cn" target="_blank"> | |
| <img src="https://user-images.onmicrosoft.cn/155082301-d777c58f-d495-42d7-8dba-59ca844379e7.jpg"> | |
| </a> | |
| <ul> | |
| <li>这个其实是从高中带过来的执念 hhh</li> | |
| <li>因为那时候很执着于励志视频,总感觉自己的生活没有斗志(</li> | |
| <li>于是趁着闲下来并且用自己的爬虫知识,搞来了 1000 多个 video </li> | |
| <li>就做出来了这个(</li> | |
| </ul> | |
| <button class="btn-next" onclick="play()">继续播放</button> | |
| </section> | |
| <section class="section-5"> | |
| <h3>#FF0 流量之最!图欧学习资源库</h3> | |
| <a href="https://tuostudy.com" target="_blank"> | |
| <img src="https://githubusercontent.onmicrosoft.cn/zkeq/Python-WebSite-Screenshot/master/save/tuostudy.com/2023-01-14_06-08-37.png"> | |
| </a> | |
| <ul> | |
| <li>这个站的日 uv 在 1w 左右() 应该算是我运维的最大的流量体了。</li> | |
| <li>虽然是公益运维,但是它带动了我的一系列周边()</li> | |
| </ul> | |
| <h5>这里是统计日志:</h5> | |
| <a href="https://site.icodeq.com/tuostudy.com" target="_blank"> | |
| <img src="https://bu.dusays.com/2023/01/15/63c2de4536cac.png"> | |
| </a> | |
| <button class="btn-next" onclick="play()">继续播放</button> | |
| </section> | |
| <section class="section-6"> | |
| <h3>#F0F 其他的我感觉倒是没什么好炫耀的啦</h3> | |
| <blockquote><h5>都是一些自己练手或者自己玩的小项目</h5></blockquote> | |
| <button class="btn-next" onclick="play()">继续播放</button> | |
| <section class="section-6_1"> | |
| <h4>#00000F 每日早报</h4> | |
| <a href="https://news.icodeq.com" target="_blank"> | |
| <img src="https://bu.dusays.com/2023/01/15/63c2d48578f96.png"> | |
| </a> | |
| <ul> | |
| <li>初学 js 的时候写的,到现在好像每天 1k 访问量的样子</li> | |
| <li>后端采用 Python 云函数 bs4 + fastapi 开发</li> | |
| <li>这个 js 写的惨不忍睹,但是勉强能用只能算是</li> | |
| </ul> | |
| <button class="btn-next" onclick="play()">继续播放</button> | |
| </section> | |
| <section class="section-6_2"> | |
| <h4>#0000F0 LearnOnly 导航重构</h4> | |
| <a href="https://learnonly.xyz" target="_blank"> | |
| <img src="https://bu.dusays.com/2023/01/15/63c2d43e74542.png"> | |
| </a> | |
| <ul> | |
| <li>这个主要是模仿 万花筒 导航站,做的自己的一个起始页。</li> | |
| <li>里面用到了 js 瀑布流,算是练习了一下基本功。</li> | |
| <li>支持导入自己的书签(如果你也有很好的文件夹分类习惯的话)</li> | |
| </ul> | |
| <button class="btn-next" onclick="play()">继续播放</button> | |
| </section> | |
| <section class="section-6_3"> | |
| <h4>#000F00 Vue2 + element 做的几个单页</h4> | |
| <a href="https://share.onmicrosoft.cn/5fa9z3zqt" target="_blank"> | |
| <img src="https://img.onmicrosoft.cn/2022/12/17/84ffc1d1-1562-4a59-b901-4206427272b2.png"> | |
| <img src="https://img.onmicrosoft.cn/2022/12/17/7e32a7e6-040a-49a5-af7b-6eb4f99fc477.png"> | |
| <img src="https://img.onmicrosoft.cn/2022/12/17/0b05a7b1-ea5f-45dc-b349-2d7c536fddb5.png"> | |
| <img src="https://bu.dusays.com/2023/01/15/63c2d4b523a60.png"> | |
| </a> | |
| <ul> | |
| <li>这些主要是写给自己用的,所以就随便写啦。</li> | |
| <li>其实自己没有学过框架,只是模糊知道一点概念。但是双向绑定真的很爽。</li> | |
| <li>安利官网的互动教程:https://cn.vuejs.org/tutorial/#step-1</li> | |
| </ul> | |
| <button class="btn-next" onclick="play()">继续播放</button> | |
| </section> | |
| <section class="section-6_4"> | |
| <h4>#00F000 自建不蒜子</h4> | |
| <a href="https://busuanzi.icodeq.com" target="_blank"> | |
| <img src="https://bu.dusays.com/2023/01/15/63c2d60e41f6a.png"> | |
| </a> | |
| <ul> | |
| <li>因为我的博客是纯静态博客,没有后台</li> | |
| <li>所以想要实现访问量统计的话,就要借助其他工具。</li> | |
| <li>不蒜子 就是一个轻量的,开箱即用的统计工具。</li> | |
| <li>但是因为其经常 502 所以我自己仿写了一个。/</li> | |
| </ul> | |
| <button class="btn-next" onclick="play()">继续播放</button> | |
| </section> | |
| <section class="section-6_5"> | |
| <h4>#0F0000 Bing-WallPage-Actions Bing 壁纸爬虫</h4> | |
| <a href="https://bing-wallpaper.apifox.cn" target="_blank"> | |
| <img src="https://img.onmicrosoft.cn/2022-09-08/image-20220909010202981.png"> | |
| </a> | |
| <ul> | |
| <li>目前已经上架 Apifox APIhub 啦(就在B站旁边)</li> | |
| <li>里面会吐出 单个 国家/地区 所有的每日图片</li> | |
| <li>目前大概 270*9 大约 2000 张图片了</li> | |
| <li>详情点击图片体验。</li> | |
| </ul> | |
| <button class="btn-next" onclick="play()">继续播放</button> | |
| </section> | |
| <section class="section-6_6"> | |
| <h4>#F00000 一些平台的部署记录留念</h4> | |
| <a href="https://github.com/zkeq" target="_blank"> | |
| <img src="https://img.onmicrosoft.cn/2022-09-08/github.com_zkeq%E7%9A%84%E5%89%AF%E6%9C%AC.png"> | |
| <img src="https://img.onmicrosoft.cn/2022-09-08/vercel.com_dashboard.png"> | |
| <img src="https://bu.dusays.com/2023/01/14/63c2ab62a9a04.png"> | |
| <img src="https://bu.dusays.com/2023/01/15/63c301f60bcaf.png"> | |
| </a> | |
| </section> | |
| <button class="btn-next" onclick="play()">重新播放</button> | |
| </section> | |
| </div> |
然后就面临了一个难题,我这是动态添加的 CSS,如何与右边的 HTML 效果 联动呢?
想了很久,最后想到的方法是首先把元素都隐藏起来,然后 CSS 挨个显示
过程中使用 js 滚动视窗:
| document.querySelector(".section-*").scrollIntoView(true); |
来实现联动。
听起来很简单,但是我实现起来却是这样的()():
| if (textStartIndex > 673 && !section_6_5){ | |
| document.querySelector(".section-6_5").scrollIntoView(true); | |
| section_6_5 = true; | |
| stop(); | |
| }else if (textStartIndex > 633 && !section_6_4){ | |
| document.querySelector(".section-6_4").scrollIntoView(true); | |
| section_6_4 = true; | |
| stop(); | |
| }else if (textStartIndex > 593 && !section_6_3){ | |
| document.querySelector(".section-6_3").scrollIntoView(true); | |
| section_6_3 = true; | |
| stop(); | |
| }else if (textStartIndex > 553 && !section_6_2){ | |
| document.querySelector(".section-6_2").scrollIntoView(true); | |
| section_6_2 = true; | |
| stop(); | |
| }else if (textStartIndex > 513 && !section_6_1){ | |
| document.querySelector(".section-6_1").scrollIntoView(true); | |
| section_6_1 = true; | |
| stop(); | |
| }else if (textStartIndex > 473 && !section_6){ | |
| document.querySelector(".section-6").scrollIntoView(true); | |
| section_6 = true; | |
| stop(); | |
| }else if (textStartIndex > 435 && !section_5){ | |
| document.querySelector(".section-5").scrollIntoView(true); | |
| section_5 = true; | |
| stop(); | |
| }else if (textStartIndex > 396 && !section_4){ | |
| document.querySelector(".section-4").scrollIntoView(true); | |
| section_4 = true; | |
| stop(); | |
| }else if (textStartIndex > 359 && !section_3){ | |
| document.querySelector(".section-3").scrollIntoView(true); | |
| section_3 = true; | |
| stop(); | |
| }else if (textStartIndex > 321 && !section_2){ | |
| document.querySelector(".section-2").scrollIntoView(true); | |
| section_2 = true; | |
| stop(); | |
| }else if (textStartIndex > 283 && !section_1){ | |
| document.querySelector(".section-1").scrollIntoView(true); | |
| section_1 = true; | |
| stop(); | |
| }else if (textStartIndex > 245 && !section_1){ | |
| document.querySelector(".section-0").scrollIntoView(true); | |
| section_0 = true; | |
| } |
只能是算出来每个代码块的 CSS 是第几个,然后判断一下,很笨,但是勉强能用吧。
后来发现这个方案有个 bug,在掘金的嵌入代码时,最外层的滚动条也会被滚动,影响用户体验
解决方案:
| document.querySelector(".section-*").scrollIntoView({block: "nearest", inline: "nearest"}); | |
| // https://stackoverflow.com/questions/56688002/javascript-scrollintoview-only-in-immediate-parent |
接着就是没什么意思的了,写了一堆适配代码,然后加上了一个好看的按钮样式:
| .btn-next { | |
| --clr-font-main: hsla(0 0% 20% / 100); | |
| --btn-bg-1: hsla(194 100% 69% / 1); | |
| --btn-bg-2: hsla(217 100% 56% / 1); | |
| --btn-bg-color: hsla(360 100% 100% / 1); | |
| --radii: 0.5em; | |
| cursor: pointer; | |
| padding: 0.3em 1.3em; | |
| margin-bottom: 10px; | |
| zoom: 0.8; | |
| min-width: 60px; | |
| min-height: 44px; | |
| font-size: var(--size, 1rem); | |
| font-family: "Segoe UI", system-ui, sans-serif; | |
| font-weight: 500; | |
| transition: 0.8s; | |
| background-size: 280% auto; | |
| background-image: linear-gradient(325deg, var(--btn-bg-2) 0%, var(--btn-bg-1) 55%, var(--btn-bg-2) 90%); | |
| border: none; | |
| border-radius: var(--radii); | |
| color: var(--btn-bg-color); | |
| box-shadow: 0px 0px 20px rgba(71, 184, 255, 0.5), 0px 5px 5px -1px rgba(58, 125, 233, 0.25), inset 4px 4px 8px rgba(175, 230, 255, 0.5), inset -4px -4px 8px rgba(19, 95, 216, 0.35); | |
| } | |
| .btn-next:hover { | |
| background-position: right top; | |
| } | |
| .btn-next:is(:focus, :focus-within,:active) { | |
| outline: none; | |
| box-shadow: 0 0 0 3px var(--btn-bg-color), 0 0 0 6px var(--btn-bg-2); | |
| } |
补充:增加了全屏检测以及切换功能:
| function launchFullScreen(element) { | |
| if(isFullScreen()){ | |
| exitScreen(); | |
| return; | |
| } | |
| if (element.requestFullscreen) { | |
| element.requestFullscreen(); | |
| } else if (element.mozRequestFullScreen) { | |
| element.mozRequestFullScreen(); | |
| } else if (element.webkitRequestFullscreen) { | |
| element.webkitRequestFullscreen(); | |
| } else if (element.msRequestFullscreen) { | |
| element.msRequestFullscreen(); | |
| } | |
| } | |
| function exitScreen() { | |
| if (document.exitFullscreen) { | |
| document.exitFullscreen(); | |
| } | |
| else if (document.mozCancelFullScreen) { | |
| document.mozCancelFullScreen(); | |
| } | |
| else if (document.webkitCancelFullScreen) { | |
| document.webkitCancelFullScreen(); | |
| } | |
| else if (document.msExitFullscreen) { | |
| document.msExitFullscreen(); | |
| } | |
| if (typeof cfs != "undefined" && cfs) { | |
| cfs.call(el); | |
| } | |
| } | |
| function isFullScreen() { | |
| return !! ( | |
| document.fullscreen || | |
| document.mozFullScreen || | |
| document.webkitIsFullScreen || | |
| document.webkitFullScreen || | |
| document.msFullScreen | |
| ); | |
| } | |
| // 来源:https://www.jianshu.com/p/6170f1ba7467 |
记录一个 scrollIntoView 的bug
- 起初我用的是
document.querySelector(".section-0").scrollIntoView(true); - 但是我发现这在掘金的嵌入代码预览时,父级页面:也就是浏览器窗口
- 居然也会跟着滚动,这不是期望发生的行为。
- 经过查阅很多资料后,使用了一个还算可以的解。
| // 也就是上文提到的 | |
| document.querySelector(".section-*").scrollIntoView({block: "nearest", inline: "nearest"}); |
参考资料
- javascript - scrollIntoView block vs inline - Stack Overflow
- html - Javascript scrollIntoView only in immediate parent - Stack Overflow
让我感到很有意思的是,一个5年前就说应该被修复的 bug 居然留存到了现在( 2023 年)









