• 原生 js 小工具 v1.1:自动生成博文目录,文内标题平滑跳转:欢迎园友试用!


    一、前言

      最近十来天都在学习原生 javascript,参考的是 《DOM Scripting》 这本英文原版书,写得确实非常不错,适合 js 基础非常不牢的小白~ 学的时候基本上是看一章就写点读书笔记发表在博客上,但却基本没动过手写代码。

      正好自己最近在写技术博文时遇到了一点小需求,就打算用 js 做一个小工具解决掉。

    二、情景

      技术人员写博客时,有一个很常见的方式就是写系列博客(或者称作主题博文),即围绕某一个中心技术点,循序渐进,由浅入深地论述其方方面面。园子里这种现象非常常见,就拿排在前列的几位老大说说,例如 Artech 的 “深入剖析授权在 WCF 中的实现[共14篇]”,例如 李永京 的 “NHibernate 之旅系列文章”,如 小洋(燕洋天) 的 “浅谈ASP.NET 的内部机制” 等等,都是其中的典范之作。

      为什么高手们似乎都有写系列博客的倾向?个人认为有以下几点好处:

    • 长时间专注于某一具体技术点,不断地深入探索
    • 当你把技术心得说给他人听时,会强迫你去重新整理自己的思路,把其中的一些盲点暴露出来,然后弄清楚
    • 对系列博文的布局安排,要求你将所学知识整理为框架、体系,对知识结构的构建非常有益
    • 好的系列博文,条理分明,非常方便以后个人参考

      呵呵,总之,系列博文的写作非常有利于个人知识的深化和整理,可以说是从小工到专家的一个比较有效的方法之一。

      我们在写系列博文时,不仅要对文章前后的安排顺序仔细斟酌,而且每一篇文章的谋篇布局也是需要花一番头脑的。随便看看上面提到的高手的系列博 文,都会感觉逻辑之分明,条理之清晰。就拿永京老大的 “NHibernate 之旅” 系列文章为例,作者在每篇博文中都用到了大量的标题,主标题下还有次级标题,这样一来把整篇文章的内容结构很好地规划出来,同时为提高读者的阅读体验,还 在每篇文章的开篇列下了文章的标题结构。不多说,上图:

      

      呵呵,读者一打开这篇文章,映入眼帘的就是整篇文章的框架结构,非常清晰并富有逻辑性,有助于把握整篇文章的脉络。从这一点可以看出,为一篇技术博文划分逻辑层次,使用主标题(mainTitle)+ 次级标题(subTitle)的形式是极其常见和实用的。

      笔者的第一个原生 js 小工具的目的,就是为了增强这种 主标题 + 次级标题 的用户体验效果。

    三、功能描述

    核心功能只有两个:

    自动生成博文目录

      如永京老大的博文,写完文章后把大大小小的标题抽出来放在文首固然是个好方法,但总觉得这样做比较麻烦。那么能否通过代码自动抽取标题,然后再包装一下插入文档中呢?

      小工具的第一个核心功能,就是为了解决上述的需求。基本实现原理:首先要求博主在写博文的时候,将主标题和次级标题用 HTML 标签中的 title tag(如:h1、h2、h3...)包起来;然后通过 JS 代码遍历整个包含博文正文的 <div>,过滤出这些标签,再把它们组装成 自定义列表 的形式,再插入到 HTML 文档中,如下:

    <dl>
    <dt>NHibernate中的查询方法</dt>
    <dt>条件查询</dt>
    <dd>创建ICriteria实例</dd>
    <dd>结果集限制</dd>
    <dd>结果集排序</dd>
    <dd>一些说明</dd>
    <dt>根据示例查询</dt>
    <dt>实例分析</dt>
    <dt>结语</dt>
    </dl>

      如果不加任何样式,它在浏览器中的默认显示效果如下:

      

      呵呵,不过我们当然可以手动为其添加 CSS 代码,后面会给出来的。

    文内标题平滑跳转

      现在假设我们已经构造出了标题的菜单,插入到了文档中。如果我想点击其中的某一项,页面就会定位到相应位置的话,这样会方便很多(特别是在以后 再次浏览的时候)。对  HTML 比较熟的童鞋可能马上想到:用锚!对,我们可以把 <a name="title"></a> 放置到想要跳转的位置,然后再用 <a href="#title"></a> 来填充菜单,那么只要点击后者,浏览器窗口就会立即切换到前者所处的位置。

      这是一个不错的解决方案,但是缺点就在于这种跳转是瞬间的,而且目的地是未知的(不知道向下跳转还是向上跳转),这样会造成非常糟糕的用户体验,会打乱读者在浏览一篇文章时所形成的连贯性。所以我们不打算使用锚,而是自己来使用 JS 实现一种平滑跳转的效果。

      实现的原理也不难:首先通过调用 DOM 方法,判断出浏览器滚动条(scroll bar)的当前位置,记为 currentPos;然后计算出目标标题(target title)的距页面顶端的距离,记为 finalPos;最后通过一定的算法实现平滑过度。

    四、源代码

    下面给出 JS 源代码和一些必要的 CSS 样式:

    JS 源代码 createContent.js

      一共有 5 个函数:

    • getPos(e):获取元素位置,即距浏览器左边界的距离(left)和距浏览器上边界的距离(top);
    • getScroll():获取滚动条当前位置;
    • moveWindow(finalpos, internal):移动滚动条,finalPos 为目的位置,internal 为移动速度;
    • moveFixElement(elementID, finalTop, finalRight, internal):移动绝对定位(absolutely、fixed)元素,elementID 表示元素 ID,finalTop 和 finalRight 表示最终位置,internal 表示移动速度
    • createContent(id, mt, st, interval):创建标题菜单,id 表示包含博文正文的 div 容器的 id,mt 和 st 分别表示主标题和次级标题的标签名称(如 H2、H3,需大写!),interval 表示移动的速度。 
      其前 4 个函数都是辅助性的函数,最后一个才是用户需要调用的创建标题菜单的函数。下面是全部的源代码:
    //获取元素位置
    function getPos(e) {
    var t = l = 0;
    while (e)
    {
    t
    += e.offsetTop;
    l
    += e.offsetLeft;
    e
    = e.offsetParent;
    }
    return {top:t, left:l};
    }

    //获取滚动条信息
    function getScroll() {
    return document.body.scrollTop | document.documentElement.scrollTop;
    }

    //移动窗体
    function moveWindow(finalpos, interval) {

    //若不支持此方法,则退出
    if(!window.scrollTo) return false;

    //窗体滚动时,禁用鼠标滚轮
    window.onmousewheel = function(){
    return false;
    };

    //清除计时
    if (document.body.movement) {
    clearTimeout(document.body.movement);
    }

    var currentpos = getScroll(); //获取滚动条信息

    var dist = 0;
    if (currentpos == finalpos) { //到达预定位置,则解禁鼠标滚轮,并退出
    window.onmousewheel = function(){
    return true;
    }
    return true;
    }
    if (currentpos < finalpos) { //未到达,则计算下一步所要移动的距离
    dist = Math.ceil((finalpos - currentpos)/10);
    currentpos += dist;
    }
    if (currentpos > finalpos) {
    dist
    = Math.ceil((currentpos - finalpos)/10);
    currentpos -= dist;
    }

    var scrTop = getScroll(); //获取滚动条信息
    window.scrollTo(0, currentpos); //移动窗口
    if(getScroll() == scrTop) //若已到底部,则解禁鼠标滚轮,并退出
    {
    window.onmousewheel
    = function(){
    return true;
    }
    return true;
    }

    //进行下一步移动
    var repeat = "moveWindow(" + finalpos + "," + interval + ")";
    document.body.movement
    = setTimeout(repeat, interval);
    }

    //移动绝对定位元素
    function moveFixElement(elementID, finalTop, finalRight, interval)
    {
    var elem = document.getElementById(elementID);
    if(elem.movement){
    clearTimeout(elem.movement);
    }

    finalTop
    = parseInt(finalTop);
    finalRight
    = parseInt(finalRight);

    var xpos = parseInt(elem.style.right);
    var ypos = parseInt(elem.style.top);

    var dist = 0;

    if(xpos == finalRight && ypos == finalTop){
    return true;
    }

    if(xpos < finalRight){
    dist
    = Math.ceil((finalRight - xpos) / 10);
    xpos
    += dist;
    }
    if(xpos > finalRight){
    dist
    = Math.ceil((xpos - finalRight) / 10);
    xpos
    -= dist;
    }

    if(ypos < finalTop){
    dist
    = Math.ceil((finalTop - ypos) / 10);
    ypos
    += dist;
    }
    if(ypos > finalTop){
    dist
    = Math.ceil((ypos - finalTop) / 10);
    ypos
    -= dist;
    }

    elem.style.right
    = xpos + "px";
    elem.style.top
    = ypos + "px";

    var repeat = "moveFixElement('" + elementID + "'," + finalTop + "," + finalRight + "," + interval + ")";
    elem.movement
    = setTimeout(repeat, interval);
    }

    //创建菜单
    function createContent(id, mt, st, interval)
    {
    //获取博文正文div容器
    var elem = document.getElementById(id);
    if(!elem) return false;

    //获取div中所有元素结点
    var nodes = elem.getElementsByTagName("*");

    //创建自定义列表
    var dlist = document.createElement("dl");

    //创建div容器
    var blogContent = document.createElement("div");
    blogContent.setAttribute(
    "id","blogContent");

    var title = document.createElement("div");
    title.setAttribute(
    "id","contentTitle");

    var dldiv = document.createElement("div");
    dldiv.setAttribute(
    "id", "dldiv");

    var num = 0;

    //遍历所有元素结点
    for(var i=0; i<nodes.length; i++)
    {
    if(nodes[i].nodeName == mt || nodes[i].nodeName == st)
    {
    //获取标题文本
    var nodetext = nodes[i].firstChild.nodeValue;
    //插入锚
    nodes[i].setAttribute("id", "blogTitle" + num);

    switch(nodes[i].nodeName)
    {
    case mt: //若为主标题
    var item = document.createElement("dt");
    break;
    case st: //若为子标题
    var item = document.createElement("dd");
    break;
    }

    //创建锚链接
    var itemtext = document.createTextNode(nodetext);
    item.appendChild(itemtext);
    item.setAttribute(
    "name", num);
    item.onclick
    = function(){ //添加鼠标点击触发函数
    var pos = getPos(document.getElementById("blogTitle" + this.getAttribute("name")));
    if(!moveWindow(pos.top, interval)) return false;
    };

    //将自定义表项加入自定义列表中
    dlist.appendChild(item);
    num
    ++;
    }
    }

    if(num == 0) return false;

    //将自定义列表加入文档中
    dldiv.appendChild(dlist);
    blogContent.appendChild(title);
    blogContent.appendChild(dldiv);
    document.body.appendChild(blogContent);

    //设置初始化位置
    blogContent.style.right = 0 - dldiv.offsetWidth + "px";
    blogContent.style.top
    = (window.innerHeight - title.offsetHeight) / 2 + "px";

    //点击 title,目录平滑伸缩
    title.onclick = function(){
    var blogContent = document.getElementById("blogContent");
    var dldiv = document.getElementById("dldiv");
    if(parseInt(blogContent.style.right) == 0){
    moveFixElement(
    "blogContent", blogContent.style.top, -dldiv.offsetWidth, 20);
    }
    else{
    moveFixElement(
    "blogContent", blogContent.style.top, 0, 20);
    }
    };

    //添加窗口 resize 事件响应
    window.onresize= function(){
    var title = document.getElementById("contentTitle");
    blogContent.style.top
    = (window.innerHeight - title.offsetHeight) / 2 + "px";
    }
    }

    CSS 样式代码

      最终生成的标题菜单(content of titles)的 HTML 结构图如下所示:

      

      所以我们可为其编写如下的 CSS 代码:

    /*三个div容器的样式*/
    #blogContent
    {
    position
    :fixed;
    }
    #contentTitle
    {
    background-image
    : url(title.png);
    background-position
    :-46px 0px;
    width
    :46px;
    height
    :148px;
    float
    :left;
    cursor
    :pointer;
    }
    #dldiv
    {
    float
    :left;
    font-family
    :'微软雅黑';
    color
    :#3191B4;
    background-color
    :#F1FAFB;
    border
    :1px dotted #3191B4;
    border-radius
    :5px;
    }
    /*自定义列表样式*/
    #dldiv dl
    {
    margin
    :8px 8px 8px 12px;

    }
    #dldiv dd,dt
    {
    cursor
    :pointer;
    }
    #dldiv dd:hover, dt:hover
    {
    color
    :#A7995A;
    }
    #dldiv dt
    {
    font-weight
    :bold;
    margin-top
    :10px;
    font-size
    :15px;

    }
    #dldiv dd
    {
    margin
    :4px 15px;
    font-size
    :12px;
    }

    五、使用方法

    写博

      最重要的当然是写博啦,如果不写博,那么这个工具就毫无存在的价值了。在写博添加标题时,首先用鼠标选定标题文字,然后点击编辑栏的样式设置菜单,选择 标题1-标题6 其中的一项。如下图所示:

      

      当然,主标题要比次级标题设得大一些,对我来说,一般选择 h2 作为主标题,h3 作为次级标题。

    添加 JS 引用

      首先在 博客后台管理->设置->公告 中添加对 js 文件的引用,如下:

      

    调用函数

      然后,在博文编辑器中点击 HTML 功能键,在博文的 HTML 源码中添加如下 js 代码:

      

      方法 createContent 即为生成标题目录的 js 函数,它有四个参数:

      "cnblogs_post_body":表示包含博文正文的 div 容器的 id;

      "H2":表示主标题的 HTML tag(需大写!);

      "H3":表示次级标题的 HTML tag(需大写!);

      20:表示平滑跳转时的速度,数值越大速度越快。

      一般来说第一个参数不用改,后面三个根据具体情况而定。

    添加 CSS 样式

      最后,我们需要对生成的标题构成的目录设置样式,包括位置、底色、字体等等,我们在 后台管理->设置->通过 CSS 设置页面样式 中添加上面列出的 CSS 代码:

      

      最终的效果,你应该在浏览本篇博文时已经看到了。

    六、结语

      这是笔者初学原生 javascript 的第一个小作品,欢迎园友试用,也欢迎提出改进建议!

    http://www.cnblogs.com/hustlzp/archive/2011/08/22/a-js-tool-for-auto-blog-title-content-generate.html

    https://files.cnblogs.com/hustlzp/createContent.js

  • 相关阅读:
    转载:揪出MySQL磁盘消耗迅猛的真凶
    转载:MySQL看这一篇就够了
    转载:MySQL:亲测备份策略实例(线上真实备份案例)
    Consul集群搭建 2Server+ 3Client
    consul配置参数大全、详解、总结
    基于consul高可用
    MySQL MGR+ Consul之数据库高可用方案
    MySQL Group Replication-MGR集群
    MySQL binlog_format中sbr 和rbr(Statement-Based and Row-Based Replication)的优缺点
    MySQL binlog2sql-闪回数据
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/2152820.html
Copyright © 2020-2023  润新知