在移动端开发中为了防止点击穿透行为,我们有时会阻止浏览器的默认行为,此时我们需要自定义所需浏览器行为,本文就介绍下自定义浏览器的滚动条。
移动端浏览器的默认行为
浏览器的m默认行为主要有以下几点:
- 上下滑动 滚动条
- 文字、图片长按选中
- 输入框获取焦点
- 用户缩放
- 模拟click动作(点击穿透)
- 下滑的空白区域
- ...
Tween算法
由于常规的css过渡和定时难以实现滚动条的即点即停效果,此时我们需引入tween算法来实现相应的效果。
参数说明:
t: current time:当前时间;
b: beginning value:初始值,元素的初始位置;
c: change in value:变化量,元素的结束位置与初始位置距离差
d: duration:持续时间,整个过渡持续时间
s:Elastic和Back 的可选参数。回弹系数,s值越大.回弹效果越远。
返回值:
元素每一次运动到的位置
/*tween类*/
var Tween = {
Linear: function(t,b,c,d){ return c*t/d + b; },
Quad: {
easeIn: function(t,b,c,d){
return c*(t/=d)*t + b;
},
easeOut: function(t,b,c,d){
return -c *(t/=d)*(t-2) + b;
},
easeInOut: function(t,b,c,d){
if ((t/=d/2) < 1) return c/2*t*t + b;
return -c/2 * ((--t)*(t-2) - 1) + b;
}
},
Cubic: {
easeIn: function(t,b,c,d){
return c*(t/=d)*t*t + b;
},
easeOut: function(t,b,c,d){
return c*((t=t/d-1)*t*t + 1) + b;
},
easeInOut: function(t,b,c,d){
if ((t/=d/2) < 1) return c/2*t*t*t + b;
return c/2*((t-=2)*t*t + 2) + b;
}
},
Quart: {
easeIn: function(t,b,c,d){
return c*(t/=d)*t*t*t + b;
},
easeOut: function(t,b,c,d){
return -c * ((t=t/d-1)*t*t*t - 1) + b;
},
easeInOut: function(t,b,c,d){
if ((t/=d/2) < 1) return c/2*t*t*t*t + b;
return -c/2 * ((t-=2)*t*t*t - 2) + b;
}
},
Quint: {
easeIn: function(t,b,c,d){
return c*(t/=d)*t*t*t*t + b;
},
easeOut: function(t,b,c,d){
return c*((t=t/d-1)*t*t*t*t + 1) + b;
},
easeInOut: function(t,b,c,d){
if ((t/=d/2) < 1) return c/2*t*t*t*t*t + b;
return c/2*((t-=2)*t*t*t*t + 2) + b;
}
},
Sine: {
easeIn: function(t,b,c,d){
return -c * Math.cos(t/d * (Math.PI/2)) + c + b;
},
easeOut: function(t,b,c,d){
return c * Math.sin(t/d * (Math.PI/2)) + b;
},
easeInOut: function(t,b,c,d){
return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b;
}
},
Expo: {
easeIn: function(t,b,c,d){
return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
},
easeOut: function(t,b,c,d){
return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
},
easeInOut: function(t,b,c,d){
if (t==0) return b;
if (t==d) return b+c;
if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
}
},
Circ: {
easeIn: function(t,b,c,d){
return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b;
},
easeOut: function(t,b,c,d){
return c * Math.sqrt(1 - (t=t/d-1)*t) + b;
},
easeInOut: function(t,b,c,d){
if ((t/=d/2) < 1) return -c/2 * (Math.sqrt(1 - t*t) - 1) + b;
return c/2 * (Math.sqrt(1 - (t-=2)*t) + 1) + b;
}
},
Elastic: {
easeIn: function(t,b,c,d,a,p){
if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3;
if (!a || a < Math.abs(c)) { a=c; var s=p/4; }
else var s = p/(2*Math.PI) * Math.asin (c/a);
return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
},
easeOut: function(t,b,c,d,a,p){
if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3;
if (!a || a < Math.abs(c)) { a=c; var s=p/4; }
else var s = p/(2*Math.PI) * Math.asin (c/a);
return (a*Math.pow(2,-10*t) * Math.sin( (t*d-s)*(2*Math.PI)/p ) + c + b);
},
easeInOut: function(t,b,c,d,a,p){
if (t==0) return b; if ((t/=d/2)==2) return b+c; if (!p) p=d*(.3*1.5);
if (!a || a < Math.abs(c)) { a=c; var s=p/4; }
else var s = p/(2*Math.PI) * Math.asin (c/a);
if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )*.5 + c + b;
}
},
Back: {
easeIn: function(t,b,c,d,s){
if (s == undefined) s = 1.70158;
return c*(t/=d)*t*((s+1)*t - s) + b;
},
easeOut: function(t,b,c,d,s){
if (s == undefined) s = 1.70158;
return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b;
},
easeInOut: function(t,b,c,d,s){
if (s == undefined) s = 1.70158;
if ((t/=d/2) < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b;
return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b;
}
},
Bounce: {
easeIn: function(t,b,c,d){
return c - Tween.Bounce.easeOut(d-t, 0, c, d) + b;
},
easeOut: function(t,b,c,d){
if ((t/=d) < (1/2.75)) {
return c*(7.5625*t*t) + b;
} else if (t < (2/2.75)) {
return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b;
} else if (t < (2.5/2.75)) {
return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b;
} else {
return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b;
}
},
easeInOut: function(t,b,c,d){
if (t < d/2) return Tween.Bounce.easeIn(t*2, 0, c, d) * .5 + b;
else return Tween.Bounce.easeOut(t*2-d, 0, c, d) * .5 + c*.5 + b;
}
}
}
使用案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
#outer {
margin: 50px;
width: 600px;
height: 40px;
border: 5px solid green;
}
#inner {
width: 0;
height: 40px;
background: #f90;
}
.btn{
margin-top: 20px;
}
</style>
</head>
<body>
<div id="outer">
<div id="inner"></div>
<div class="btn">
<button class="btn1">匀速</button>
<button class="btn2">减速</button>
<button class="btn3">回弹</button>
</div>
</div>
<script>
//定义Tween算法的函数
var tween = {
//匀速
linear: function(t,b,c,d){ return c*t/d + b; },
//减速
cubicEaseOut: function(t,b,c,d){
return c*((t=t/d-1)*t*t + 1) + b;
},
//回弹
backEaseOut: function(t,b,c,d,s){
if (s === undefined) s = 1.70158;
return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b;
},
};
/*
* Tween 函数的参数
* t current time 当前时间
* b beginning value 初始值
* c change in value 值的变量量(目标值-初始值)
* d duration 持续时间
* s 回弹系数
* */
var inner = document.querySelector('#inner');
var btn1 = document.querySelector('.btn1');
var btn2 = document.querySelector('.btn2');
var btn3 = document.querySelector('.btn3');
btn1.onclick = function () {
var t = 0;
var b = 0;
var c = 600;
var d = 5000;
var intervalId = setInterval(function(){
t += 10;
var value = tween.linear(t, b, c, d); //匀速
inner.style.width = value + 'px';
// 如果当前的时间点,已经到达了持续总时间 听了
if (t >= d) {
clearInterval(intervalId);
}
}, 10);
};
btn2.onclick = function () {
var t = 0;
var b = 0;
var c = 600;
var d = 5000;
var intervalId = setInterval(function(){
t += 10;
var value = tween.cubicEaseOut(t, b, c, d); //减速
inner.style.width = value + 'px';
// 如果当前的时间点,已经到达了持续总时间 听了
if (t >= d) {
clearInterval(intervalId);
}
}, 10);
};
btn3.onclick = function () {
var t = 0;
var b = 0;
var c = 600;
var d = 5000;
var intervalId = setInterval(function(){
t += 10;
var value = tween.backEaseOut(t, b, c, d); //回弹
inner.style.width = value + 'px';
// 如果当前的时间点,已经到达了持续总时间 听了
if (t >= d) {
clearInterval(intervalId);
}
}, 10);
};
</script>
</body>
</html>
自定义屏幕滚动条
//自定义屏幕滚动插件 touchScroll.js
(function(w){
/**
* 实现屏幕滚动
* @param wrapper 包裹元素
* @param content 内容元素(负责位置变化)
* @param scrollBar 滚动条元素;可选;如果不指定,就没有滚动条
*/
function touchScroll(wrapper, content, scrollBar){
var intervalId = null; //定义定时器标记
//计算滚动条的高度
if (scrollBar) {
var scale1 = wrapper.clientHeight / content.offsetHeight; //比例
scrollBar.style.height = wrapper.clientHeight * scale1 + 'px';
}
//开启3d加速
transformCss(content, 'translateZ', 0);
//触摸开始
wrapper.addEventListener('touchstart', function(event){
//获取触点对象
var touch = event.targetTouches[0];
// 获取触点的起始位置
this.startY = touch.clientY;
// 获取 content 的起始位置
this.eleY = transformCss(content, 'translateY');
// 初始化 触点滑动距离
this.dstY = 0;
//初始时间
this.startTime = Date.now();
//滚动条显示
if (scrollBar) {
scrollBar.style.opacity = 1;
}
//取消定时
clearInterval(intervalId);
});
//触摸移动
wrapper.addEventListener('touchmove', function(event){
//获取触点对象
var touch = event.targetTouches[0];
//获取触点的结束位置
var endY = touch.clientY;
//计算触点的滑动距离
this.dstY = endY - this.startY;
// 根据滑动距离 计算 content 的位置
var translateY = this.eleY + this.dstY;
//判断临界值,开启橡皮筋效果
if (translateY >= 0) { //上边界
//计算比例
var scale = 1 - translateY / (wrapper.clientHeight * 1.9);
//重新计算translateY
translateY *= scale;
} else if (translateY <= (wrapper.clientHeight - content.offsetHeight)) {
//计算content距离视口底部距离
var bottomY = wrapper.clientHeight - (content.offsetHeight + translateY);
// 计算比例
var scale = 1 - bottomY / (wrapper.clientHeight * 1.9);
// 重新计算 bottomY
bottomY *= scale;
// 重新计算 translateY
translateY = (wrapper.clientHeight - bottomY) - content.offsetHeight
}
// 设置 content 的位置
transformCss(content, 'translateY', translateY);
// 调整滚动条位置
if (scrollBar) {
setScrllBarOffset(translateY);
}
});
//触摸结束
wrapper.addEventListener('touchend', function(event){
//结束时间,计算时间差
var dstTime = Date.now() - this.startTime;
//计算加速距离
var speed = this.dstY / dstTime * 200;
// 计算此时元素的位置 并且加上 加速距离
var translateY = transformCss(content, 'translateY');
translateY += speed;
//设置过渡类型
var type = 'cubicEaseOut'; //一直在减速
//判断到达临界点,开启回弹
if (translateY >= 0) {
translateY = 0;
type = 'backEaseOut';
} else if (translateY <= wrapper.clientHeight - content.offsetHeight) {
translateY = wrapper.clientHeight - content.offsetHeight;
type = 'backEaseOut';
}
//重新设置 content 位置 调用过渡函数
moveTo(content, translateY, 500, type);
});
/**
* 过渡函数
* @param node 要发生变换的元素
* @param target 目标值
* @param duration 过渡持续时间
* @param type 过渡类型 linear、cubicEaseOut、backEaseOut
*/
function moveTo(node, target, duration, type = 'linear') {
//定义Tween算法的函数
var tween = {
//匀速
linear: function(t,b,c,d){ return c*t/d + b; },
//减速
cubicEaseOut: function(t,b,c,d){
return c*((t=t/d-1)*t*t + 1) + b;
},
//回弹
backEaseOut: function(t,b,c,d,s){
if (s === undefined) s = 1.70158;
return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b;
},
};
// 定义Tween函数的参数
var t = 0; //当前时间
var b = transformCss(node, 'translateY'); //起始值
var c = target - b; //变化量
var d = duration;
//避免多次定时,清除之前的定时(不论之前有没有定时)
clearInterval(intervalId);
//开启定时
intervalId = setInterval(function(){
//t变换
t += 10;
//利用Tween函数获取当前的值
var translateY = tween[type](t,b,c,d);
// 设置content 的位置
transformCss(node, 'translateY', translateY);
//调整滚动条的位置
if (scrollBar) {
setScrllBarOffset(translateY);
}
//判断过渡完毕
if (t >= d) {
//定时结束 过渡结束
clearInterval(intervalId);
//滚动条隐藏
if (scrollBar) {
scrollBar.style.opacity = 0;
}
}
}, 10);
}
/**
* 设置滚动条的位置
* @param contentTranslateY 内容此时的位置
*/
function setScrllBarOffset(contentTranslateY) {
// 计算比例 content当前位置 / 最大位置
var scale2 = -contentTranslateY / (content.offsetHeight - wrapper.clientHeight);
// 计算滚动条位置
transformCss(scrollBar, 'translateY', (wrapper.clientHeight - scrollBar.offsetHeight) * scale2);
}
}
//把方法暴露
w.touchScroll = touchScroll;
})(window);
使用案例-竖向滑动
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0,viewport-fit:cover">
<title>自定义竖向滑动</title>
<style>
* {
margin: 0;
padding: 0;
}
html,body,#app {
width:100%;
height: 100%;
overflow: hidden;
}
#content {
font-size: 26px;
}
#scrollBar {
position: absolute;
top: 0;
right: 0;
width: 4px;
height: 100px;
border-radius: 2px;
background-color: rgba(0,0,0,.8);
opacity: 0;
transition: opacity .2s;
}
</style>
</head>
<body>
<div id="app">
<div id="content"></div>
<div id="scrollBar"></div>
</div>
<script src="js/transformcss.js"></script>
<script src="js/touchscroll.js"></script>
<script>
(function () {
var app = document.querySelector('#app');
var content = document.querySelector('#content');
var scrollBar = document.querySelector('#scrollBar');
app.addEventListener('touchstart', function(event){
event.preventDefault();
});
for (var i = 0; i <= 200; i ++) {
content.innerHTML += i + '<br>';
}
touchScroll(app, content, scrollBar);
})();
</script>
</body>
</html>
注:为了更方便简洁的实现触摸移动,因此在自定义滚动条(touchScroll.js)时使用了transformcss.js文件,因此在使用自定义滚动条插件时需先引入transformcss.js,地址:https://segmentfault.com/a/11...,或直接查看笔者上一篇文章: 封装transfrom函数。
说明:笔者只是个在前端道路上默默摸索的初学者,若本文涉及错误请及时给予纠正,如果本文对您有帮助的话,点击铭哥哥网址 http://learn.fuming.site/ 学习更多!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。