FE玩耍专栏(一)

1. toggle button

See the Pen toggle button via css by lu (@luchen) on CodePen.

小记:
有几个重要点呐:

  • input 的id与 label的for属性对应,使得label可以检测input的check事件,否则点label是不能触发check的。
  • css3中:checked 属性,” + “兄弟选择符。使得可以对选中状态下的label进行css编写
  • todo 用transform,尽量不用left这些。以达到平滑效果 【done】
    确实过渡会平滑很多。

2. scrollbar

See the Pen js实现滚动条 by lu (@luchen) on CodePen.

recipe:

  • scrollTop
  • height(div高度)
  • 文档内容高度(如果是普通的box里面的,则ele.scrollHeight)

这边是以一个小的div为例。

如果想全局滚动条,类似于阮一峰es6教程上的那种效果。

则应该相应的变为

1
2
3
$(window).scrollTop()
$(window).height()
$(document).height()

补充知识:

  • element.scrollTop是滚动过程中文档超出视窗的部分,它的最大值就是document.height-element.height,即为progressbar为100%的时候。
    两幅图可以说明:

  • 还有一种纯css做的方法
    CSS only scroll indicator。思路主要就是用一个斜对角的图形,然后z-index =-1,只留出height为比如2px的区域,这样滚动条动时,显示的部分就在慢慢变宽。


3. 模拟操作

很久以前我萌还在用jquery的时候,很喜欢用的一个函数是trigger,但是离开了jquery,还有什么方法可以实现同样的问题,楼主一直很困惑…一直到看了mdn的相关api,具体demo请戳自定义事件及触发
划重点就是:

1
2
3
var event = new Event()
target.addEventListener("自定义名称")
target.dispatchEvent(event);

See the Pen trigger event by lu (@luchen) on CodePen.

references:

  1. 创建和触发 events
  2. mouseevent

4. css做tab切换

todo

  • background-clip: 裁剪,从 border-box开始。
  • background-origin: 显示图片,从padding-box开始
    to summary:
  1. background详解
  2. css div p(div下的所有子元素,包括孙子), div>p(div的直接子元素中的p),div+p(div的直接后兄弟,1个),div~p(div后面的所有p兄弟)

5. slider

楼主在开发这部分组件,用的是原生的html5的input中的type=range,实现了这么几个功能:

  • 滑动+tooltip
  • 左侧部分高亮
  • step滑动
  • 显示step tick
    基本按照ant-design的接口来写的。但是遇到一个尴尬的问题就是…没有办法实现范围选择…需要重新开发..

recipe:

html5 input(type:range)
选择器:


6. 无限滚动

楼主最近在用vue开发cnode的移动端,首页部分有滚动列表,因为我们之前提到过debounce延缓input框发送ajax联想请求。那么这边我们就需要用throtte去实现无限滚动。(无限滚动的原理就是在滚动事件+分页请求+节流)
具体为什么不用debounce,需要体会的是debounce是在停止项某事件或者超过某个限时时才执行一次。滚动并不需要用户在停止scroll才去加载,而是应该在滚动的过程中定时去请求。所以用throtte。
throtte的实现是最简单的版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function throtte(func,wait,options){
let lastTime = Date.now();
return function next(){
const now = Date.now();
const gap = now - lastTime;
if( gap >= wait ){
func.apply(this,arguments);
// if(param===options.terminal) return;
lastTime = now;
}
}
}
//调用
created(){
this.fetchPage(1);
this.infiniteScroll = this.infiniteScroll.bind(this);
window.addEventListener("scroll",this.infiniteScroll());
},
infiniteScroll(){
return throtte(this.fetchWhenScroll.bind(this),1000);
},

在实现过程中,还有这么几个问题:

  • 楼主最开始的思路:方向检测+缓存。直接在滚动时发送请求(给定page初始值,根据上下滑动方向page++/page–),同时把每一页的数据存储下来。下次发送请求前,先根据页数判断是否已有缓存。上滑的话,不请求,满足需求。上滑一段时间后下滑,因为该页数已经访问过,数据已有,那么也不请求。满足需求。

    所以,现在的关键问题在于:怎么检测这个scroll的上下顺序
    查阅了资料,发现普遍是通过前后两次的scrollTop来检测的。楼主写了一个

    1
    2
    3
    4
    5
    6
    7
    8
    function isScrollDown(initTop,cb){
    const curTop = document.documentElement.scrollTop;
    let isDown = false;
    if(curTop>initTop){
    isDown = true;
    cb(curTop);
    return isDown;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//调用,滚动检测
fetchWhenScroll(){
const isDown = isScrollDown(this.initTop,(curTop)=>{
this.initTop = curTop;
);
isDown? this.page++ : this.page--; //1,方向检测
this.fetchPage(this.page);
},
fetchPage(page){
if(responseDict[page]) return; //2,缓存判断
axios.get('https://cnodejs.org/api/v1/topics',{
params:{
page,
...
}
})
.then(response=>{
if(response.data){
const appendedList = this.getTransformedResponse(response.data);
responseDict[page] = appendedList; //3,缓存数据
this.postList = [...this.postList,...appendedList];
}
})
.catch(err=> console.log(err));
}

scrollTop是文档的top=0处位置,到window的top=0位置的间距。具体的可以看上面scrollbar的示意图
但是仔细一想,实际上是有问题的。当下滑时,会不停的page++,fetch serverlist。当文档实际上已经结束时。由于还在滚动,page会继续++。,所以要加一个if(response.data)的判断。如果没有数据,就啥也不干。

  • 第二种思路,是看一篇无限滚动的文章看到的(Debouncing and Throttling Explained Through Examples)。主要原理是检测离底部的距离。当快接近底部时发送请求。

    视窗底部距离文档底部的距离:
    let height = \$(document).height() - \$(window).scrollTop() -$(window).height();

    应用场景

    • 实现回到顶部: scrollTop ===0
    • 检测底部: scrollTop === documentHeight- windowHeight
      具体的关于scroll,楼主上面的scrollbar里面也有介绍。可以瞄一眼。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function check_if_needs_more_content(){
//1
const scrollTop = document.body.scrollTop||document.documentElement.scrollTop;
//2
const doc = document.documentElement;
const name = 'Height';
const docHeight = Math.max(
document.body[ "scroll" + name ], doc[ "scroll" + name ],
document.body[ "offset" + name ], doc[ "offset" + name ],
doc[ "client" + name ]
);
//3
const windowHeight = window.innerHeight || document.documentElement.clientHeight;
let distanceToDocBottom = docHeight-windowHeight-scrollTop;
if(Math.abs(distanceToDocBottom)<300) return true;
return false;
}
//调用
fetchWhenScroll(){
if(check_if_needs_more_content()){
this.fetchPage(this.page);
this.page++;
}
},

这种方法的好处在于,避免方向的判断,上滑的时候 distanceToDocBottom实际上是变大的,那么就不需要进行请求。同时由于距离底部的距离限定好,那么从本次加载数据,到下次加载数据,实际上是把本次获得的数据滚动完以后再进行加载的。但是有一点问题是,貌似滑动到最下面的时候会停止,需要上滑一部分才会响应。而且当滚动很多页的时候,distanceToBottom会大于设定的gap。就不会发送请求。待解决。
除此之外,因为公式是jquery版本的。楼主试了很多兼容的原生版本,但是都不太对…就暗搓搓的去github上面搜了jquery这部分的源码…捂脸。
补充

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if ( jQuery.isWindow( elem ) ) {
return elem.document.documentElement[ "client" + name ];
}
// Get document width or height
if ( elem.nodeType === 9 ) {
doc = elem.documentElement;
// Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height], whichever is greatest
// unfortunately, this causes bug #3838 in IE6/8 only, but there is currently no good, small way to fix it.
return Math.max(
elem.body[ "scroll" + name ], doc[ "scroll" + name ],
elem.body[ "offset" + name ], doc[ "offset" + name ],
doc[ "client" + name ]
);
}
  • 优化(todo):
    • 缓存dom节点。
    • 用raf代替throtte,比较滑动的平滑效果。用debounce对比下效果。

===2016/11/23 更新
楼主的throtte是有点问题的,看了下underscore的函数说明,有两点:

  • lastTime的初始值:
    api的本意是一调用这个throtte才会设置lasttime,然后在此基础上去进行+wait.而楼主是在创建这个闭包的时候就把lasttime给定义了。也就是说如果
    let test = throtte(console.log.bind(this,3),3000)。
    在新建test后立刻test()执行。是不会运行的。因为达不到时间。
    如果过一段时间test().那么才会执行。违背了立刻执行的本意。
  • 2.原api中有leading,trailing的设置:
    leading是指一调用test(),是否会立即执行(false表明不立即执行)。trailing是指,(trailing的前提是设置了settimeout,在0-wait内进入的函数都会被取消到最后再执行)。那么是否在最后真正执行,就是靠这个trailing。注意。leaing和trailing是不可以同时设置false的。因为这样的话介个函数永远都不会执行=。=,头尾都拒绝了呢。。。下面po下楼主现在的版本。

    1. next函数中的this丢失,fix
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
function throtte(func,wait,options){
let isFirst = true;
let funcId,gap,lastTime,duration = 0;
const finalOptions = Object.assign({},{
leading: true,
trailing: true
},options);
if(!finalOptions.leading && !finalOptions.trailing) {
alert("不可以同时设置leading,trailing为负");
return;
}
return function next(){
const now = Date.now();
const context = this;
if( isFirst || (gap = now -lastTime) >= wait ){
//=====更新 2017.1.5
lastTime = now;
if(isFirst){
isFirst = false;
//leading:false时,不执行,等下一次wait的时间以后
if(finalOptions.leading)
func.apply(context,arguments);
}
}
else{
// lastTime-lastTime+wait间进入的函数,需要取消。结束再执行
clearTimeout(funcId);
//trailing true时才执行。
if(finalOptions.trailing)
funcId = setTimeout(function(){
func.apply(context,arguments); //!!context
lastTime = now;
}, wait-gap);
}
}
}

css3常用

  • 伪类
    • :focus
    • :checked
    • :target