当青训营遇上码上掘金 | 会动的名片

217 阅读8分钟

当青训营遇上码上掘金 | 会动的名片

作为一个前端工程师🦁️,这次选择的是 主题 1:我的名片,用代码来介绍自己,这太酷了。

话不多说,先看 👀 成品:

小屏不大好看,可以在 code.juejin.cn 或者 self-introduction.icodeq.com 查看全屏幕版本

代码仓库:github.com/zkeq/Doraem…

项目思路

看到题目之后,原本是想要仿写一个闪光 📸️ 卡片的:poke-holo.simey.me

image.png

但是因为技术栈不同,其实是不知道怎么抄。

只能退而求其次,用我之前写过一个 会动的代码:哆啦A梦 作为模版

详情看这里,也是上次的掘金入围作者啦(只是过了初审而已hh):

🤔,大概就是这样

image.png

其实 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. Github90+ 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"});

参考资料

让我感到很有意思的是,一个5年前就说应该被修复的 bug 居然留存到了现在( 2023 年)

image.png

image.png