仿掘金官网_文章详情页 | 青训营笔记

134 阅读3分钟

这是我参与「第四届青训营」笔记创作活动的的第38天

效果展示

QQ录屏20220824180422_.gif QQ录屏20220824180103_.gif

源代码

gitee.com/lyn20011115…

问题描述

动态的响应式布局

当缩放界面时,动态地展示导航栏、侧边栏、文章主体与右侧目录。

markdown 文档解析与代码高亮

安装 markdown-loader,之后在 vue.config.js 文件内添加配置并重新启动项目,进行文章解析;

npm i html-loader markdown-loader --save-dev

安装 highlight.js,并新建工具文件 highlight.js,进行代码高亮处理。

npm install --save highlight.js

侧边栏、文章与目录的吸顶效果

监听滚动条,滚动到顶部导航栏消失时,侧边栏和右侧目录就会定位。 position: fixed

目录生成、点击跳转与滚动高亮效果

分析文章,将 h1 ~ h6 标题的内容提取存放到数组中,通过列表循环展示。监听页面的滚动,当滚动到对应标题所在位置时,高亮对应的目录内容。

功能实现

动态的响应式布局

纯 CSS 实现的简单布局。参考:CSS 网站布局

/* 创建三个不相等的彼此并排的浮动列 */
/* 左列 */
.leftcolumn {
  float: left;
  width: 15%;
}
/* 其余同理 */

/* 清除列之后的浮动 */
.row:after {
  content: "";
  display: table;
  clear: both;
}

/* 响应式布局 - 当屏幕的宽度小于 1400 像素时,使三列堆叠而不是并排 */
@media screen and (max-width: 1400px) {
  .maincolumn {
    width: 75%;
    padding: 0;
  }
  .rightcolumn {
    width: 20%;
    padding-left: 20px;
  }
  .leftcolumn,
  .menu-item,
  .img {
    display: none;
  }
  .menu {
    width: 100%;
  }
}
/* 其余同理,分别在屏幕宽度小于 1400、1100、600 像素时响应 */

markdown 文档解析与代码高亮

尝试了很多 markdown 文档解析与代码高亮的插件,但都没有成功。最终使用的是以下这个:一站式解决vue解析markdown,代码高亮,目录获取

侧边栏、文章与目录的吸顶效果

// JS
mounted () { //渲染成html后调用
  window.addEventListener('scroll', this.initHeight) //监听窗口的滚动
  this.$nextTick(() => {
    //左侧边栏距离它的 offsetParent 元素的顶部的距离
    this.offsetTop = document.querySelector('#left').offsetTop
  })
}

initHeight () {
  //获取当前页面滚动条纵坐标的位置
  const scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
  //滚动到侧边栏位置时将这三列固定
  this.isFixed = scrollTop > this.offsetTop
}
// HTML
<div class="leftcolumn" id="left" :class="{'is_fixed' : isFixed}">···</div>
// CSS
.is_fixed {
    position: fixed;
    float: left;
    width: 15%;
}

文章与目录的效果同理。

目录生成、点击跳转与滚动高亮效果

目录:

<ul>
  <li
    v-for="(nav, index) in contents"
    :key="index"
    @click="handleMenuList(nav)">
    <a :class="{ 'itemHight': menuIndex === index }">{{nav}}</a>
  </li>
</ul>

目录生成:

搜索获得的文章,将其中的 h1 ~ h6 标题的内容存入数组 contents[] 中,然后使用循环列表显示。

···
if (this.content[i].localName === 'h1') {
···
  this.contents.push(this.content[i].innerHTML)
···
}
//其余标题同理

点击跳转

handleMenuList (nav) { //传入当前点击的目录内容
  // 目录内容在目录数组中的索引位置,会高亮对应位置的目录
  this.menuIndex = this.contents.findIndex((item) => item === nav)
  // 获取目录内容在文章中的索引位置
  this.clickMenuIndex = this.article.findIndex((item) => item === nav)
  const navPage = this.content[this.clickMenuIndex]
  // 滚动到对应位置
  scrollTo(0, navPage.offsetTop)
}

滚动高亮

同样监听页面滚动,通过比较 标题到顶部的距离滚动距离 + 导航栏距离 来确定哪条目录应该高亮。

initHeight () {
  for (let i = this.menuIndex; i < this.content.length; ++i) { //从当前所在位置开始
    if (this.content[i].offsetTop < scrollTop + 40) {
      // 当标题快要滚动到顶部时
      if (this.content[i].localName === 'h1' || this.content[i].localName === 'h2' || this.content[i].localName === 'h3' || this.content[i].localName === 'h4' || this.content[i].localName === 'h5' || this.content[i].localName === 'h6') {
        // 获得当前滚动到的标题在目录数组中的位置
        this.menuIndex = this.contents.findIndex((item) => item === this.content[i].innerHTML)
      }
    }
  }
}

后续

虽然和组内的另一个小伙伴按时完成了项目,但因为没来得及填写项目报告并将项目部署到服务器(第一次部署服务器不太了解遇到了很多问题),导致没有参加最后的项目答辩,有点遗憾。但总的来说这次活动也让我这个前端小白了解到了很多东西,还是受益匪浅的,非常感谢各位老师们♪(・ω・)ノ。

ps:因为时间紧迫所以没来得及优化代码,源代码中有些冗余的代码也没来得及删除,部分注释有误,查阅时请自行注意区分啦~