Vue自适应滚动条触底更新 发表于 2017-04-23 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384<template> <div id="box"> <scrollbar @bottom="bottom" @top="top" :changeTop.sync="changeTop"> <div id="container"> <p>Vue自定义滚动条</p> <p v-for="n in 30">自定义滚动条{{n}}</p> <div v-for="(str,index) in data">add data-{{str}}-{{index}}</div> </div> </scrollbar> <div id="loadingbox" v-show="isloading">loading...</div> </div></template><script> import scrollbar from './4' export default { components: { scrollbar }, data() { return { data: [], isloading: false, changeTop: 0, } }, methods: { top() { console.log('top'); }, bottom() { console.log('bottom') this.isloading = true; var newdata = [ '我是新加的数据', '我是新加的数据', '我是新加的数据', '我是新加的数据', '我是新加的数据', '我是新加的数据', '我是新加的数据', '我是新加的数据', '我是新加的数据', '我是新加的数据', ]; setTimeout(() => { this.data = this.data.concat(newdata); this.isloading = false; }, 1000); } }, }</script><style> * { padding: 0px; margin: 0px; } #box { margin: 0 auto; text-align: center; width: 500px; height: 500px; border: 1px solid red; border-radius: 3px; position: relative; } #container { width: 100%; } #loadingbox { width: 100%; background: rgba(255, 255, 255, .8); position: absolute; left: 0; bottom: 0; text-align: center; font-weight: bold; }</style> 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289<template> <div ref="box" class="scrollbar_box" @wheel="scroll" @mouseenter="mouseenter($event)" @mouseleave="mouseleave($event)"> <div ref="container" class="scrollbar_container" :style="{ transform : `translate(0px, ${top*-1}px)` }"> <slot></slot> </div> <div ref="scrollbarPath" class="scrollbar_path" v-show="isVerticalBtn" :class="{on: verticalCur}"> <div ref="verticalBtn" class="scrollbar_verticalBtn" :style="{ height: `${barHeight}%`, top : `${barTop}%` }" @mousedown="startmoveV" > </div> </div> </div></template><script> export default { data() { return { top: 0, //box偏移量px barHeight: 0, //竖直滚动条高度 barTop: 0, //竖直滚动条上下偏移量% isVerticalBtn: false, //竖直滚动条是否显示 verticalCur:false, isStartmoveV: false, //点击拖拽垂直标志位 point: { //相对拖快左上角的x,y坐标 x: 0, y: 0, }, boxPoint: { // 4个角容器的坐标 minX: 0, maxX: 0, minY: 0, maxY: 0, }, isrun: false, //是否在运行 优化 因为 频繁 触发 resize 函数,导致页面很卡的 问题 节流函数 throttleTime: 400, //节流函数定时器时间 } }, props: { speed: { type: Number, default: 30 }, changeTop: { //上下偏移量 px type: Number, default: 0 }, timeOut: { //延时调用changeWinSize type: Number, default: 0 }, }, watch: { changeTop() { // 当外面传这个发送变化时就让到这个位置 this.setbarTop(); } }, computed: {}, methods: { mouseenter(){ this.verticalCur=true; }, mouseleave(){ setTimeout(()=>{ this.verticalCur=false; },1000) }, // 初始化 getPropData() { this.setbarTop(); }, setbarTop() { if(this.top == this.changeTop) return false; let size = this.getSize(); let topEnd = size.containerHeight - size.boxHeight; if(this.changeTop < 0) { this.top = 0; } else if(this.changeTop >= topEnd) { this.top = topEnd; } else { this.top = this.changeTop; } this.barTop = ((this.top * 100) / size.containerHeight) * 1; }, // 滚动 scroll(e) { if(this.isVerticalBtn) { e.preventDefault(); e.stopPropagation(); } else { return false; } let speed = this.speed; let shifted = e.shiftKey let scrollY = e.deltaY > 0 ? speed * 1 : speed * -1; if(shifted && e.deltaX == 0) scrollX = e.deltaY > 0 ? speed : speed * -1; let nextY = this.top * 1 + scrollY; // 如果没有垂直的滚动条就滚动横向的 if(this.isVerticalBtn) { this.setVerticalScroll(nextY) } }, // 垂直btn点击后的事件 startmoveV(e) { this.verticalCur=true; e.preventDefault(); //阻止默认事件,取消文字选中 let _point = this.windowToBox(e.clientX, e.clientY, this.$refs.verticalBtn); //得出来的是相对这垂直btn的点位置 this.isStartmoveV = true; this.point.x = _point.x; this.point.y = _point.y; document.addEventListener('mousemove', this.fnMousemoveV, false); document.addEventListener('mouseup', this.fnMouseup, false); }, // 垂直移动监听 fnMousemoveV(e) { this.verticalCur=true; e.preventDefault(); if(this.isStartmoveV) { this.setVerticalClick(e.clientY - this.point.y); } }, // 鼠标抬起监听 fnMouseup(e) { e.preventDefault(); this.isStartmoveV = false; this.clearMousemove(); }, // 清除监听 clearMousemove() { document.removeEventListener('mousemove', this.fnMousemoveV, false); document.removeEventListener('mouseup', this.fnMouseup, false); }, // 包围盒的信息坐标轴 windowToBox(x, y, el) { let _bbox = el.getBoundingClientRect(); return { x: x - _bbox.left, y: y - _bbox.top } }, getSize() { // 返回盒子尺寸 let _container = this.$refs.container; let _box = this.$refs.box; let size = { containerHeight: _container.scrollHeight, //滚动内容的高度宽度 containerWidth: _container.scrollWidth, boxHeight: _box.clientHeight, //最外面盒子的高度宽度 boxWidth: _box.clientWidth, } return size; }, // 点击拖拽设置 setVerticalClick(val) { let size = this.getSize(); let barTop = ((val - this.boxPoint.minY) / this.$refs.box.clientHeight) * 100; //换算成百分比 if(barTop <= 0) { barTop = 0; if(this.barTop == barTop) { return false; } this.$emit('top'); } if(barTop + this.barHeight >= 100) { barTop = 100 - this.barHeight; if(this.barTop == barTop) { return false; } this.$emit('bottom'); } this.barTop = barTop; // 这里是百分比的需要转换 this.top = ((barTop / 100) * size.containerHeight).toFixed(2) * 1; //换算百分百 this.$emit('update:changeTop', this.top); }, setVerticalScroll(val) { //val是偏移量 滚动的 let size = this.getSize(); let topEnd = size.containerHeight - size.boxHeight; if(val >= topEnd) { val = topEnd; if(this.top == val) { // 已经到底部就不用继续执行了 return false; } this.$emit('bottom'); }; if(val <= 0) { val = 0; if(this.top == val) { // 已经到顶部就不用继续执行了 return false; } this.$emit('top'); } this.top = val; this.$emit('update:changeTop', this.top); this.barTop = (val / size.containerHeight) * 100; // 导航条的top的计算 }, changeWinSize() { //最重要的一个方法,触底计算,滑块的top不变,只改变高度 let size = this.getSize(); let boxPoint = this.$refs.box.getBoundingClientRect(); // container的极坐标 // 保存box窗口坐标 this.boxPoint.minX = boxPoint.left; this.boxPoint.maxX = boxPoint.right; this.boxPoint.minY = boxPoint.top; this.boxPoint.maxY = boxPoint.bottom; // 计算拖拽条的宽高 this.barHeight = (size.boxHeight / size.containerHeight) * 100; //100是百分比,box的高度是100%,比例计算 // 是否显示拖拽条 this.isVerticalBtn = (this.barHeight >= 100 && !!this.barHeight) ? false : true; if(!this.isVerticalBtn) { this.top = 0; this.barTop = 0; } } }, mounted() { this.$nextTick(() => { this.getPropData(); this.changeWinSize(); }); }, updated() { //由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子 this.$nextTick(() => { if(this.timeOut != 0) { //防止某些动画影响如el-collapse-transition setTimeout(() => { this.changeWinSize(); }, this.timeOut); } else { this.changeWinSize(); } }); } }</script><style scoped lang="less"> .scrollbar_box { overflow: hidden; position: relative; height: 100%; width: 100%; transform: translateZ(0); backface-visibility: hidden; perspective: 1000; transform: translate3d(0, 0, 0); .scrollbar_container { overflow: visible; width: 100%; height: 100%; } .scrollbar_path { position: absolute; top: 0px; right: 0px; width: 6px; height: 100%; background-color: white; opacity: 0; transition: opacity 500ms; &.on{ opacity: 1; } .scrollbar_verticalBtn { position: absolute; top: 0px; right: 0px; width: 6px; border-radius: 3px; background-color: #51555e; cursor: pointer; z-index: 50; &:hover { background-color: green;z-index: 51; } } } }</style>