用原生 js && jquery 实现知乎收起答案功能

Build Statusnpm

  • Update 2016.12.7
  • 已封装为插件javascript

    • 原生 js 插件$ npm install foldcontent-zhihu@">=3.0.12" --save Usage
    • jquery 插件 $ npm install foldcontent-zhihu-jquery@">=1.0.1" --save Usage

  • Update 2016.11.23
  • 此前demo 定义两个按钮,一个固定定位,一个绝对定位,所以滚动至底部时会出现两个按钮同时出现的问题,用户体验不是很好,所以更改成只有一个按钮,判断滚动至底部时添加 classname 更改样式。且精简了代码ヾ(≧∇≦*)ゝ
  • scroll 事件性能优化问题。css

    • 鼠标滚动时 scroll 事件触发的间隔大约为 10~20 ms,相对于其余的鼠标、键盘事件,它被触发的频率很高,间隔很近。所以若是 scroll 事件涉及大量的位置计算、元素重绘等工做,且这些工做没法在下个 scroll 事件触发前完成,就会致使浏览器掉帧
    • 所以须要减小绑定给 scroll 中具体想要执行的业务逻辑的执行次数
    • 并将对象初始化、不变的高度值等缓存在 scroll 事件外部
    • 存在 bug : 当以很快的速度滚动时,有可能执行不到 scroll 绑定的事件(ó﹏ò),有没得更好的优化方案?
  • 示例代码及 github 均已更新 ٩(ˊᗜˋ*)و

  • Update 2016.11.22
  • 优化代码结构,存在命名不规范、jquery 方法和原生 js 方法混用、代码未封装、设计冗余等问题。。。review 代码被批了。。。
    图片描述

没有需求的话……就本身提一个 ୧(๑•̀⌄•́๑)૭

  • 起初是要作一个公司内部的 mongoDB 日志查询网站,前端用 bootstrap,后端用 nodejs 作了一个简单的页面,不得不说页面仍是很粗陋的,由于一条日志的内容不少,若是直接显示的话内容太过冗长,每每滚动几回才能看完一条日志,并且常常查询的就是固定的几个 key,直接展现不利于迅速debug。
  • 问题肯定了,要实现的就是显示日志时只显示常常查询的几个键值对,点击展开时显示所有日志,点击收起时变回原状。
  • 需求很简单,并且和知乎的显示所有&&收起功能很是类似,可是 Google 了一下没有找到相似的 demo,所以决定本身实现一个!

Here we go

  • 看了知乎的网页代码。原理是点击显示所有时,若是这条答案超出浏览器视窗,则收起按钮变成固定定位,js 计算出 right 值,bottom固定为12px;当这条答案底部滚动至浏览器视窗内,收起按钮变回绝对定位。ps 发现知乎是否是改版了,以前答案底部出如今浏览器视窗内后这个位置是有收起按钮的?,按钮从固定定位变为绝对定位并更改样式,就像旁边的做者保留权利这样的风格~(图一直传不上来。。暂时放弃了)
  • 如今的作法是直接隐藏掉固定定位的收起按钮。

clipboard.png

  • 其实个人实现方法和知乎的不甚相同,由于他的 js 代码我真心……没看懂!谁能告诉我这种状况应该怎么调试(ノ°ο°)ノ

clipboard.png

  • 因此想了另外一种思路,在答案右下角定义一个按钮 A,判断答案顶部和底部的相对位置 x 和 y ,其中 y = x + 答案的高度(js 获取)。当答案出如今浏览器视窗内,即 x = $(window).height() 时,给 A 添加固定定位,动态定义 right 值;当答案即将滚出浏览器视窗,即 y = $(window).height() 时,A 变为相对定位,right 值始终为 20px。当 A 的文本内容为收起时,点击 A 文本内容变为展开,去掉固定定位。
  • 此处省去链接数据库等无关工做,仅用两段有趣的文字做为 demo ~
  • 首先,文字内容分为 all-content 和 part-content 两部分,分别为折叠前和折叠后要展现的内容,由于还未搞懂知乎折叠答案后显示哪一部份内容的算法,因此简单粗暴地分了折叠前和折叠后的内容。。此处有一个 TODO ?
<ul class="wrap">
    <li>
        <div class="content all-content" style="display: none;">
            <h2>Sheldon 座位理论</h2>
            <p>In the winter that seat is close enough to the radiator to remain warm 
            and yet not so close as to cause perspiration. 
            In the summer it's directly in the path of a cross breeze 
            created by opening windows there, and there. 
            It faces the television at an angle that is neither direct, 
            thus discouraging conversation, 
            nor so far wide to create a parallax distortion. I could go on,
            but ... I think I've made my point.</p>
            <p>冬季的时候,这个地方离电暖器很近、最暖和,可是又不会近到让你感受热、流汗;
                夏天的时候,这里又能够吹由那两扇窗户吹进来的徐徐微风;
                坐这的角度可让我同时看电视又同时和他人聊天而不受影响,
                刚好不会太远也不会产生视觉上的错觉。</p>
            <p>That is my spot. In an ever-changing world it is a simple point of consistency.
                If my life were expressed as a function in a four-dimensional Cartesian coordinate system,
                that spot, at the moment I first sat on it, would be 0000.</p>
            <p>那是个人专座。在这个不断变化的世界里,这是不变的一点。
                假设个人生命用一个创建在四维直角坐标系里的方程来表示的话,
                这个座位从我坐上那一刻开始就成为了(0,0,0,0)。</p>
        </div>

        <div class="content part-content">
            In the winter that seat is close enough to the radiator to remain warm 
            and yet not so close as to cause perspiration. 
            In the summer
            <b> ...</b>
        </div>

        <div class="sign unfold">展开</div>
    </li>
</ul>
var doc = $(document);
var win = $(window);
// 屡次使用, 缓存起来
doc.on('click', '.unfold', function () {
    var unfold = $(this);
    if (unfold.text() !== '收起') {
        unfold.text('收起').siblings('.part-content').hide().siblings('.all-content').show();
        var panel = unfold.parent();
        var panelScroll = panel.offset().top + panel.height();
        var scrollHeight = doc.scrollTop() + win.height();
        var right = win.width() / 2 - 350 + 20 > 20 ? win.width() / 2 - 350 + 20 : 20;
        // 点击展开按钮时即判断是否出现收起按钮
        if (scrollHeight - panelScroll < 50) {
            unfold.addClass('fold-fix').css('right', right);
        }
        // 绑定在 scroll 事件上
        var cb = {
            onscroll: function() {
                var panelScroll = panel.offset().top + panel.height();
                var scrollHeight = doc.scrollTop() + win.height();
                var right = win.width() / 2 - 350 + 20 > 0 ? win.width() / 2 - 350 + 20 : 20;
                if (scrollHeight - panelScroll < 50 &&
                    panel.offset().top - scrollHeight < -90 && unfold.text() !== '展开') {
                    unfold.addClass('fold-fix').css('right', right);
                } else {
                    changeStyle(unfold);
                }
                win.off("scroll", cb.onscroll);
                setTimeout(function() {
                    win.on("scroll", cb.onscroll);
                }, 50);
            }
        };
        win.on("scroll", cb.onscroll);
    } else {
        var fold = $(this);
        changeStyle(fold);
        fold.text('展开').siblings('.part-content').show()
            .siblings('.all-content').hide();
    }
});

function changeStyle(i) {
    i.removeClass('fold-fix').css('right', '20px');
}
  • 此处涉及一个知识点:网页元素的绝对位置 && 相对位置html

    • 网页元素的绝对位置,指该元素的左上角相对于整张网页左上角的坐标。jquery 中 offset() 方法返回元素相对于文档的偏移。该方法返回的对象包含两个整型属性:top 和 left。x.offset().top 即为 x 元素的绝对高度;
    • 网页元素的相对位置,指该元素左上角相对于浏览器窗口左上角的坐标。绝对位置减去页面的滚动条滚动的距离就是相对位置。x.offset().top - $(document).scrollTop() 即为 x 元素的相对高度。
    • 本例须要浏览器视窗刚刚滚动至答案a的绝对定位按钮出现的效果,所以此节点为答案底部的相对高度减去浏览器视窗高度正好等于负的按钮 A 的高度。即 a.offset().top + a.height() - $(document).scrollTop() - $(window).height() = - 按钮高度
    • 阮一峰老师的用Javascript获取页面元素的位置这篇文章讲解得十分清晰,若是要深刻了解这个知识点建议看一下这篇文章,说不定就有茅塞顿开的感受哦 ٩(ˊᗜˋ*)و
  • 如何动态设置固定定位的折叠按钮 的 right 值呢?前端

    • 答案的固定宽度是 700px,所以浏览器视窗宽度减去 700px 再除以 2 便始终是答案的 right 值,由于按钮为绝对定位时 right: 20px; 所以 right: $(window).width()/2 - 350 + 20 就保证了固定定位和绝对定位时按钮都在一条垂直线上,过渡衔接很天然。
    • 当浏览器窗口不断缩小时,上面计算出的固定定位时按钮的 right 值可能为负,这显然不符合需求,所以要设置当计算出的 right 值为负时设置 right 值为 20px。

catch the code

  • 源代码已上传至 my github (ㆆᴗㆆ) ?
相关文章
相关标签/搜索