项目实战 | 青训营笔记

113 阅读5分钟

项目实战 | 青训营笔记

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

项目简介:

本次完成的项目为仿掘金网页版页面,是第四届字节跳动青训营的项目作业。依靠pythonflask框架完成后端,以及主要用前端三件套HTML, CSS, JavaScript大致实现掘金前端的网页布局。

该项目为大致复现掘金网页版首页外观,主要功能及细节,如鼠标悬停时组件的效果,文章列表无限下滑加载,打开文章时内容实现代码效果高亮,文章目录随文章滚动而变化(未实现)等。

tips: 项目使用原生js写,所以有些地方非常的繁琐。

项目功能截图:

  • header主导航栏的效果样式:

bandicam 2022-08-25 10-28-25-054 00_00_12-00_00_25~1.gif

  • aside侧边栏的样式:

bandicam 2022-08-25 11-20-38-414 00_00_00-00_00_30.gif

  • 一键回到顶部按钮

bandicam 2022-08-25 10-28-25-054 00_01_16-00_01_25.gif

  • 文章列表的无限下滑

bandicam 2022-08-25 10-28-25-054 00_01_05-00_01_19~1.gif

  • 文章类别导航栏点击时文章列表的相应变化:

bandicam 2022-08-25 10-28-25-054 00_00_31-00_00_51.gif

  • 文章详情页的访问:

bandicam 2022-08-26 13-22-39-651 00_00_06-00_00_19.gif

  • 文章内容:

bandicam 2022-08-26 13-19-38-951 00_00_19-00_00_30.gif

  • 文章页面一键返回顶部按钮:

bandicam 2022-08-26 15-35-37-194 00_00_00-00_00_30.gif

  • 文章详情页下方的相关推荐:

其实数据同首页文章列表的数据一样。

bandicam 2022-08-26 13-34-30-031 00_00_06-00_00_13.gif

  • 相关推荐文章列表的跳转:

bandicam 2022-08-26 13-34-51-299 00_00_03-00_00_20.gif

项目实现:

这里只列举一部分:

前端界面之HTML和CSS:

因为本项目为仿掘金网页,尽量跟掘金网页版ui贴近,因为能力有限时间有限且不重复造轮子的原则,项目html结构与掘金网页相同,css静态样式也与掘金官网相同。但要实现一些动态的效果还得需要js的加入

前端界面之js

aside侧边栏的动态样式:

使用过掘金官网的都会发现,当滚动条滚动到侧边栏完全消失时,侧边栏中的广告栏会重新出现,并且如fixed定位一般固定于页面右上方。 但其实看过官网的html后,会发现并不是通过改变组件定位去实现该样式,而是通过设置opacity(不透明度)去实现该效果。

$(window).scroll((e) => {
    // 计算滚动条距离顶部的距离
    let scrollTop = $(document).scrollTop();
    let temp = document.querySelector(".sidebar-block.sticky-block")
    if(scrollTop>1200){
        temp.style.opacity = "1";
        let img = temp.querySelectorAll(".sticky-banner .banner-image")
        img[0].height = "200";
        img[1].height = "200";
    }else{
        temp.style.opacity = "0";
        let img = temp.querySelectorAll(".sticky-banner .banner-image")
        img[0].height = "0";
        img[1].height = "0";
    }
})

当滚动到指定位置时(如滚动位置大于某长度时),设置某组件的opacity属性为1;其他情况重新设置opacity为0.

右下方一键返回顶部的组件按钮的隐藏和显示也是同样道理。

一键返回顶部的按钮:

点击右下角返回顶部按钮会返回页面顶部,其实代码很简单:

document.querySelector(".btn1.to-top-btn").addEventListener('click', (e)=>{
    document.documentElement.scrollTop = 0;
})

documentElement 属性以一个元素对象返回一个文档的文档元素。HTML 文档返回对象为HTML元素。这里直接设置整个文档对象的scrollTop为0,就会直接返回网页顶部。

文章无限下滑:

tips: 代码中出现的article_html是文章块组件的html代码字符串,这里省略:

let requestFlag = false;
let sum = 0;

$(function(){
    rendering(17);
})

//加载文章列表
function rendering(size){
    for (let i = sum; i < sum+size; i++) {
        let article_div = $(article_html)[0];
        read(i, article_div);
        $('.entry-list')[0].appendChild(article_div);
    }
    sum+=size;
}

//读取并写入json数据信息
function read(i, div1){
   $.getJSON("../../static/resource/json/all_article_info.json", function (data){
        let temp = Object.values(data);
        let lens = temp.length;
        let temp_book = temp[i%lens];
        let article_id = temp_book["article_id"];
        let user_id = temp_book["user_id"];
        let cover_image = temp_book["cover_image"];
        let time_count = temp_book["collect_count"];
        let time = (new Date().getTime()) - (1000 * 60 * 60 * 24 * Number(time_count));
        let article_title = div1.querySelector('.article_title');
        let article_abs = div1.querySelector('.abstract div');
        article_title.innerHTML = temp_book["title"];
        article_title.title = temp_book["title"];
        article_abs.innerHTML = temp_book["brief_content"];
        article_abs.title = temp_book["brief_content"];
        div1.querySelector('.date').innerHTML = getDistanceDay(time);
        div1.querySelector('.view span').innerHTML = temp_book["view_count"];
        div1.querySelector('.like span').innerHTML = temp_book["digg_count"];
        div1.querySelector('.comment span').innerHTML = temp_book["comment_count"];
        div1.querySelector('.tag_list .tag1').innerHTML = temp_book["category_name"];
        div1.addEventListener('click', function (){window.open('/'+article_id, '_blank')});
        if(cover_image!==''){
           let img_html = "<img alt="" class="lazy thumb" loading="lazy">";
           let img = $(img_html)[0];
           img.alt = temp_book["title"];
           img.src = cover_image;
           div1.querySelector('.content-wrapper').appendChild(img);
        }

       //获取文章作者账号信息
       $.getJSON("../../static/resource/json/all_user_info.json", function (data){
            let temp_id = data[user_id];
            div1.querySelector('.popover-box.user-popover').appendChild(document.createTextNode(temp_id["name"]));
       })
   });
}

//添加滚动条监听事件监听事件
$(window).scroll((e) => {
    // 计算滚动条距离底部的距离
    let navHeight = 178;
    let scrollHeight = $(".entry-list").outerHeight();
    let windowHeight = $(window).height();
    let scrollTop = $(document).scrollTop();
    let scrollBottom = scrollHeight - scrollTop - windowHeight + navHeight;
    // console.log(scrollBottom);
    if(scrollBottom < 100 && !requestFlag){
      request();
    }
})

//请求函数
function request(){
    requestFlag = true;
    setTimeout(() => {
      requestFlag = false;
      rendering(7);
    }, 500);
}

代码代码主要包括三个功能函数:request,read, rendering

  • request函数主要是在固定时间内重复调用渲染文章块组件的函数rendering。

  • read函数主要是打开json文件,按照需求将json信息插入html代码之中,需要插入的是第几条json数据则要看rendering参数是多少。

  • rending函数中的参数代表需要渲染多少个文件组件,其将html字符串初始化后($(article_html)[0]),再调用read函数,将所需要渲染的组件下标及初始化的文章块dom对象传给read函数,然后在read函数之中打开json文件,将第i条json数据插入dom对象。

  • 检查是否该渲染下一批文章块的方法是,给窗口添加监听器,当滚动条离窗口在一定距离时,调用request函数加载下一批文章块组件。滚动条离窗口底部的距离计算公式为:scrollHeight - scrollTop - windowHeight + navHeight ,意为文章列表组件的总长度-滚动条离窗口顶部的距离 - 窗口的大小(滚动条的长度)+ 文章导航栏的高度。 这里加上文章导航栏的高度是因为文章块组件并非从窗口的最顶部开始,若不加则会出现偏差,提前加载文章块组件。

  • 代码中的文章块组件的数据插入代码如:

div1.querySelector('.view span').innerHTML = temp_book["view_count"];

这里的temp_book是json文件解析后形成的字典,temp_book["view_count"]是通过keyview_count得到文章的浏览数。这里的json文件数据来自掘金官网首页(爬取),并且是经过整理的数据。

  • 关于本代码中出现的getDistanceDay() 函数:
let time_count = temp_book["collect_count"];
let time = (new Date().getTime()) - (1000 * 60 * 60 * 24 * Number(time_count));
div1.querySelector('.date').innerHTML = getDistanceDay(time);

在上面的代码片段中,没有写出getDistanceDay(),该函数是将文章发布的日期转换成如 几天前, 几月前, 几年前 这样的样式。

markdown文件转为html:

本项目将markdown文件转html使用的是marked插件,实现代码高亮使用的是highlight插件。

但其实掘金官网使用的是markdown解析器marked以及掘金文章风格渲染接口。由于接触前端的时间太短(八月份才开始),根本就不会用vue组件,但这两个插件的初始化使用的都是vue,所以本次项目没有实现掘金的文章页样式。

启动marked插件的方式很简单:

var rendererMD = new marked.Renderer();
marked.setOptions({
    renderer: rendererMD,
    gfm: true,
    tables: true,
    breaks: false,
    pedantic: false,
    sanitize: false,
    smartLists: true,
    smartypants: false
});
marked.setOptions({
    highlight: function (code) {
        return hljs.highlightAuto(code).value;
    }
});

function loadMD() {
    var response;
    response = new XMLHttpRequest();
    response.open("GET", "{{ url_for('static', filename='articles_file/'+article_inf['article_id']+'.md') }}", true);
    response.send();
    response.onreadystatechange=function() {
        if (response.readyState===4 && response.status===200) {
            $('.markdown-body').append(marked(response.responseText));
        }
    };
}
loadMD();
// $('code').each((e)=>{
//    let code = $(this).text();
//    let highCode = hljs.highlightAuto(code).value;
//    $(this).html(highCode);
// });

在初始化marked后,第一个setOptions中的参数解释如下:

  • gfm 它是一个布尔值,默认为true。 允许 Git Hub标准的markdown.

  • tables 它是一个布尔值,默认为true。 允许支持表格语法。该选项要求 gfm 为true

  • breaks 它是一个布尔值,默认为false。 允许回车换行。该选项要求 gfm 为true

  • pedantic 它是一个布尔值,默认为false。 尽可能地兼容 markdown.pl的晦涩部分。不纠正原始模型任何的不良行为和错误。

  • sanitize 它是一个布尔值,默认为false。 对输出进行过滤(清理),将忽略任何已经输入的html代码(标签) smartLists 它是一个布尔值,默认为false。 使用比原生markdown更时髦的列表。 旧的列表将可能被作为pedantic的处理内容过滤掉.

  • smartypants 它是一个布尔值,默认为false。 使用更为时髦的标点,比如在引用语法中加入破折号。

第二个setOptions的作用是设置代码高亮,需要下载highlight插件,可以直接搜索highlight去官网下载。

loadMD函数的作用是请求md文件,使用的是ajax。

项目中的Flask

本项目涉及的flask部分并不多,仅仅是涉及到模板继承和对应路由跳转。

附录:

以下都是flask教程:

flask教程: shouce.jb51.net/flask0.10/q…

flask文档: flask.net.cn/