在用angular1.x作为项目的前台开发框架时,无法避免的会遇到需要在项目里使用第三方插件的情况(比如用来绘制表格或统计图),有时候插件的功能并不能满足我们的需要(比如绘制表格和绘图插件不包含滚动条,而表格或图形的数据比较多,我们希望用滚动条的方式来展示),需要我们自己来扩展,下面就用一个自定义滚动条插件来抛砖引玉。
首先说明一下应用场景:
页面有一个固定区域(高度和宽度固定,除窗口大小发生变化的情况外相对父元素的位置不变)需要展示一个表格,由于展示区域的位置和大小固定,因此可展示的信息数量也是固定的,但实际上需要展示的信息数量是可变的,有可能超出表格可展示的固定信息数量,而表格本身并不支持滚动条(这里假定表格使用的是第三方插件,不支持滚动条),所以我们需要自己定义一个滚动条来让用户可以控制插件展示哪一部分信息。
接下来看一下滚动条效果:
然后上代码:
scrollBarTest.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>scrollBar</title>
<script type="text/javascript" src="angular.min.js"></script>
<script type="text/javascript" src="jquery-1.9.1.min.js"></script>
</head>
<body ng-app="app" ng-controller="ctrl" id="frm">
<table id="testTable" style="width: 60px; height: 240px;"></table>
<scroll-bar></scroll-bar>
</body>
<script>
var app = angular.module("app", []);
app.controller("ctrl", ["$scope", function($scope) {
$scope.data = ["北京", "上海", "杭州", "南京", "西安", "广州", "重庆", "昆明", "海口", "香港", "天津", "南昌", "贵州", "石家庄", "深圳"];
}]);
app.directive("scrollBar", [function(){
return {
restrict: "E",
templateUrl: "scrollBar.html",
link: function($scope, $element , $attrs , ctrl){
$scope.left = ""; //滚动条相对父元素的左偏移
$scope.top = ""; //滚动条相对父元素的上偏移
$scope.height = ""; //滚动条完整长度
$scope.barPercent = ""; //滚动条可滚动部分长度占完整长度百分比
$scope.topPercent = ""; //滚动条可滚动部分相对完整滚动条的上偏移百分比
var wheelTimer = null; //监听器对象
var maxXStepsLength = 10; //每屏显示最大记录数
var data = $scope.data; //所有数据
var topNum = 0; //顶端被隐藏记录数
var barFlag = false; //鼠标点击滚动条标识
var lastClientY = 0; //上一次y轴坐标
/**
* 处理当前窗口的鼠标滚动
* */
function mouseWheelHandler(event){
/**
* IE Chorme等浏览器下使用onmousewheel事件,使用wheelDelta判断滚动方向,向下滚时wheelDelta为-120
* FireFox下不存在mousewheel事件 ,滚动时会触发DOMMouseScroll事件,通过event.detail判断滚动方向,如果event.detail>0为往下滚
*
* **/
var wheelDirective = event.wheelDelta || -event.detail;
if(wheelDirective < 0 ){
if(topNum < data.length - maxXStepsLength) {
topNum = topNum + 1;
$scope.topPercent = topNum / data.length * 100 + "%";
}
} else {
if(topNum > 0) {
topNum = topNum - 1;
$scope.topPercent = topNum / data.length * 100 + "%";
}
}
$scope.$apply();
//重新绘图
rePaint("rePaint");
}
//初始化函数 初始化滚动条可滚动部分相对完整滚动条的上偏移百分比、滚动条相对父元素的左偏移、滚动条相对父元素的上偏移、滚动条完整长度 监听鼠标滚轮事件(包括滚动条和图形本身)
$scope.init = function () {
rePaint();
//初始化滚动条高度(可滚动部分)
if(maxXStepsLength <= data.length) {
$scope.barPercent = maxXStepsLength/data.length * 100 + "%";
}
//初始化滚动条相对父元素的左偏移(这里需要根据页面的实际情况调整算法)
$scope.left = parseInt($("#testTable").css("width")) + parseInt($("#frm").css("margin-left")) + "px";
//初始化滚动条相对父元素的上偏移(这里需要根据页面的实际情况调整算法)
$scope.top = parseInt($("#frm").css("margin-top")) + "px";
//初始化滚动条完整长度(这里需要根据页面的实际情况调整算法)
$scope.height = parseInt($("#testTable").css("height")) + "px";
//初始化鼠标滚轮监听事件
initListener();
};
$scope.init();
$scope.mouseDown = function (e) {
lastClientY = e.clientY;
barFlag = true;
document.onmouseup = function () {
barFlag = false;
document.onmousemove = null;
document.onmouseup = null;
return false;
};
document.onmousemove = function (e) {
if(barFlag) {
if(wheelTimer){
clearTimeout(wheelTimer);
wheelTimer = null;
}
wheelTimer = setTimeout(function(){
mouseMoveHandler(e.clientY);
},10);
}
return false;
}
};
function mouseMoveHandler(clientY){
//向下拖动
if(clientY > lastClientY) {
if(topNum < data.length - maxXStepsLength && Math.abs(clientY - lastClientY) > parseInt($scope.height) / data.length) {
topNum = topNum + parseInt(Math.abs(clientY - lastClientY) / (parseInt($scope.height) / data.length));
if(topNum > data.length - maxXStepsLength) {
topNum = data.length - maxXStepsLength;
}
$scope.topPercent = topNum / data.length * 100 + "%";
$scope.$apply();
//重新绘图
rePaint("rePaint");
lastClientY = clientY;
}
//向上拖动
} else {
if(topNum > 0 && Math.abs(clientY - lastClientY) > parseInt($scope.height) / data.length) {
topNum = topNum - parseInt(Math.abs(clientY - lastClientY) / (parseInt($scope.height) / data.length));
if(topNum < 0) {
topNum = 0;
}
$scope.topPercent = topNum / data.length * 100 + "%";
$scope.$apply();
//重新绘图
rePaint("rePaint");
lastClientY = clientY;
}
}
}
//初始化监听
function initListener() {
/**
* IE chorme safari等浏览器
* */
$element[0].onmousewheel = function(event){
event = event||window.event;
event.stopPropagation?event.stopPropagation():event.cancelBubble=false;
event.preventDefault?event.preventDefault():event.returnValue=false;
// wheelDelta < 0时表明是往下滚; wheelDelta >= 0时是往上滚
if(wheelTimer){
clearTimeout(wheelTimer);
wheelTimer = null;
}
wheelTimer = setTimeout(function(){
mouseWheelHandler(event);
},10);
return false;
};
/**
* Firefox
* */
$element[0].addEventListener("DOMMouseScroll" , function(event){
event = event||window.event;
event.stopPropagation?event.stopPropagation():event.cancelBubble=false;
event.preventDefault?event.preventDefault():event.returnValue=false;
// wheelDelta < 0时表明是往下滚; wheelDelta >= 0时是往上滚
if(wheelTimer){
clearTimeout(wheelTimer);
wheelTimer = null;
}
wheelTimer = setTimeout(function(){
mouseWheelHandler(event);
},10);
return false;
},false);
//为图形增加鼠标滚动监听
document.getElementById("testTable").onmousewheel = function(event){
event = event||window.event;
event.stopPropagation?event.stopPropagation():event.cancelBubble=false;
event.preventDefault?event.preventDefault():event.returnValue=false;
// wheelDelta < 0时表明是往下滚; wheelDelta >= 0时是往上滚
if(wheelTimer){
clearTimeout(wheelTimer);
wheelTimer = null;
}
wheelTimer = setTimeout(function(){
mouseWheelHandler(event);
},10);
return false;
};
document.getElementById("testTable").addEventListener("DOMMouseScroll" , function(event){
event = event||window.event;
event.stopPropagation?event.stopPropagation():event.cancelBubble=false;
event.preventDefault?event.preventDefault():event.returnValue=false;
// wheelDelta < 0时表明是往下滚; wheelDelta >= 0时是往上滚
if(wheelTimer){
clearTimeout(wheelTimer);
wheelTimer = null;
}
wheelTimer = setTimeout(function(){
mouseWheelHandler(event);
},10);
return false;
},false);
}
/**
* 重新绘制受滚动条控制部分,从topNum开始画,画maxXStepsLength个
*
* flag:rePaint表示当前是重新绘制,需要清空原绘制信息
* */
function rePaint(flag) {
var table = document.getElementById("testTable");
if(flag === "rePaint") {
var rowNum = table.rows.length;
for(var i=rowNum-1;i>-1;i--) {
table.deleteRow(i);
}
}
for(var i=0;i<maxXStepsLength;i++) {
var newRow = table.insertRow();
var newCell = newRow.insertCell();
newCell.innerHTML = data[i+topNum];
}
}
}
};
}]);
</script>
</html>
scrollBar.html:
<div class="pivot-scroll-content">
<div class="scroll-bar" ng-style="{'left':left,'top':top,'height':height}" style="width: 12px; z-index: 1; position: absolute; display: block; cursor: pointer; color: #333;">
<div class="inner-scroll-bar" ng-style="{'height': barPercent, 'top': topPercent}" style="position: absolute; display: block; background: #ddd; border-radius: 3px; width: 100%; cursor: pointer;" ng-mousedown="mouseDown($event)"></div>
</div>
</div>
简单的说,就是通过鼠标滚轮事件和拖拽事件来计算和触发图形的重绘。如果是换了其他插件(比如是用插件来绘图),那么相应的则需要重写rePaint函数。另外,上面给的代码还不支持窗口的缩放,要支持的话需要在window.onresize里重新计算滚动条位置,否则一缩放滚动条位置就错了。
angular.min.js下载地址:angular.min.js
jquery-1.9.1.min.js不贴下载地址了,自己找去吧,多的要屎。
注意:代码放在本地跑的时候需要设置浏览器允许跨域,因为定义的directive的html模板是另一个html文件,以chrome为例,从命令行启动的时候需要加–allow-file-access-from-files参数,如下图:
否则会报下面这样的错:
想要整理更多的碎片知识,扫码关注下面的公众号,让我们在哪里接着唠!