加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
xscrollbar.js 11.42 KB
一键复制 编辑 原始数据 按行查看 历史
清晨de阳光 提交于 2021-06-21 16:21 . 圆角默认等于滑块大小
/*!
* x-scrollbar 自定义滚动条插件
* 版本: v3.1.0
* 作者: 清晨的阳光(QQ:765550360)
* 许可: MIT
* https://gitee.com/xujz520/x-scrollbar
*/
class XScrollbar {
constructor(dom, options) {
this.$dom = dom;
if (this.$dom.classList.contains('x-scrollbar')) return;
this.$dom.classList.add('x-scrollbar');
// 移动端检测
this.isMobile = window.navigator.userAgent.toLowerCase().indexOf('mobile') != -1;
// 合并配置
let defaultOptions = {
// 响应容器和内容大小改变(自动更新滚动条)
autoUpdate: true,
// 阻止向上传递滚动事件
preventDefault: true,
// 仅水平滚动(拨动鼠标滚轮时将作用于X轴)
onlyHorizontal: false,
// 自动隐藏
autoHide: true,
};
let defaultStyle = {
// 滑块大小
thumbSize: '5px',
// 轨道颜色
trackBackground: '#ddd',
// 滑块颜色
thumbBackground: '#5f5f5f',
// 滑块圆角大小
thumbRadius: '5px',
};
Object.assign(this, defaultOptions, defaultStyle, options);
// 构造dom
let scrollLeft = this.$dom.scrollLeft;
let scrollTop = this.$dom.scrollTop;
this.$container = this.html2dom('<div class="x-scrollbar__container"></div>');
this.$content = this.html2dom('<div class="x-scrollbar__content"></div>');
this.$trackX = this.html2dom('<div class="x-scrollbar__track-x"></div>');
this.$trackY = this.html2dom('<div class="x-scrollbar__track-y"></div>');
this.$thumbX = this.html2dom('<div class="x-scrollbar__thumb-x"></div>');
this.$thumbY = this.html2dom('<div class="x-scrollbar__thumb-y"></div>');
this.$trackX.appendChild(this.$thumbX);
this.$trackY.appendChild(this.$thumbY);
let childNodes = [];
Array.prototype.forEach.call(this.$dom.childNodes, function (node) { childNodes.push(node) });
childNodes.forEach((function (node) { this.$content.appendChild(node); }).bind(this));
this.$container.appendChild(this.$content);
this.$dom.appendChild(this.$container);
// 处理内边距
let styleObj = getComputedStyle(this.$dom);
let padding = `${styleObj.paddingTop} ${styleObj.paddingRight} ${styleObj.paddingBottom} ${styleObj.paddingLeft}`;
if(padding != '0px 0px 0px 0px') {
this.$dom.style.padding = '0px 0px 0px 0px';
this.$container.style.padding = padding;
}
// 设置初始值
this.$container.scrollLeft = scrollLeft;
this.$container.scrollTop = scrollTop;
if (this.preventDefault) {
this.$container.classList.add('x-scrollbar__container--preventDefault');
}
if (this.isMobile) return;
this.$dom.appendChild(this.$trackX);
this.$dom.appendChild(this.$trackY);
this.$container.classList.add('x-scrollbar__container--hideScrollbar');
if (JSON.stringify(defaultStyle) != JSON.stringify(Object.keys(defaultStyle).reduce((obj, k) => ({ ...obj, [k]: this[k] }), {}))) {
this.style();
}
// 自动隐藏
if (!this.autoHide) this.$dom.classList.add('x-scrollbar-keep');
// 绑定事件
this.bindScroll();
this.bindDrag();
if (this.onlyHorizontal) {
this.bindWheel();
}
// 响应容器和内容大小改变
if (this.autoUpdate) {
// 首次自动触发
this.resizeObserver();
} else {
this.update();
}
}
/**
* 设置滑块大小
*/
setThumbSize() {
// (clientWidth / scrollWidth) = (滑块大小 / clientWidth)
// 最大滑动距离 = clientWidth - 滑块大小
// 最大滚动距离 = scrollWidth - clientWidth
// (滑动距离 / 最大滑动距离) = (滚动距离 / 最大滚动距离)
// 容器大小
this.clientWidth = this.$container.clientWidth;
this.clientHeight = this.$container.clientHeight;
// 内容大小
this.scrollWidth = this.$container.scrollWidth;
this.scrollHeight = this.$container.scrollHeight;
//是否存在滚动条
this.hasXScrollbar = this.scrollWidth > this.clientWidth;
this.hasYScrollbar = this.scrollHeight > this.clientHeight;
//滑块大小
this.thumbXWidth = Math.max((this.clientWidth / this.scrollWidth) * this.clientWidth, 30);
this.thumbYHeight = Math.max((this.clientHeight / this.scrollHeight) * this.clientHeight, 30);
//最大滑动距离
this.thumbXMaxLeft = this.clientWidth - this.thumbXWidth;
this.thumbYMaxTop = this.clientHeight - this.thumbYHeight;
//最大滚动距离
this.maxScrollLeft = this.scrollWidth - this.clientWidth;
this.maxScrollTop = this.scrollHeight - this.clientHeight;
this.$trackX.style.display = this.hasXScrollbar ? 'block' : 'none';
this.$trackY.style.display = this.hasYScrollbar ? 'block' : 'none';
this.$thumbX.style.width = this.thumbXWidth + 'px';
this.$thumbY.style.height = this.thumbYHeight + 'px';
}
/**
* 拖动事件
*/
bindDrag() {
// 上一次的拖动位置
let screenX = null;
let screenY = null;
this.$thumbX.addEventListener('mousedown', (e) => {
this.$trackX.classList.add('x-scrollbar__track--draging');
this.thumbXActive = true;
screenX = e.screenX;
});
this.$thumbY.addEventListener('mousedown', (e) => {
this.$trackY.classList.add('x-scrollbar__track--draging');
this.thumbYActive = true;
screenY = e.screenY;
});
document.addEventListener('mouseup', (e) => {
this.$trackX.classList.remove('x-scrollbar__track--draging');
this.$trackY.classList.remove('x-scrollbar__track--draging');
this.thumbXActive = false;
this.thumbYActive = false;
});
document.addEventListener('mousemove', (e) => {
if (!(this.thumbXActive || this.thumbYActive)) return;
e.preventDefault();
requestAnimationFrame(() => {
if (this.thumbXActive) {
let offset = e.screenX - screenX;
screenX = e.screenX;
let left = Math.max(Math.min((parseFloat(this.$thumbX.style.left || 0) + offset), this.thumbXMaxLeft), 0);
this.$thumbX.style.left = left + 'px';
this.$container.scrollLeft = left / this.thumbXMaxLeft * this.maxScrollLeft;
} else {
let offset = e.screenY - screenY;
screenY = e.screenY;
let top = Math.max(Math.min((parseFloat(this.$thumbY.style.top || 0) + offset), this.thumbYMaxTop), 0);
this.$thumbY.style.top = top + 'px';
this.$container.scrollTop = top / this.thumbYMaxTop * this.maxScrollTop;
}
});
});
}
/**
* 仅水平滚动(拨动鼠标滚轮时将作用于X轴)
*/
bindWheel() {
let easeout = (start, end) => {
if (Math.abs(end - start) <= 1) return end;
return start + (end - start) / 4;
};
this.$container.addEventListener('wheel', (e) => {
// 仅响应 y 滚动 => 作用于 x
if (!this.hasXScrollbar) return;
if (e.deltaY && !e.shiftKey) {
// 结束值
this.scrollLeft = Math.max(Math.min((this.scrollLeft || this.$container.scrollLeft) + (e.deltaY > 0 ? 100 : -100), this.maxScrollLeft), 0);
this.left = this.scrollLeft / this.maxScrollLeft * this.thumbXMaxLeft;
// 阻止向上传递 || !(终点)
if (this.preventDefault || !(this.scrollLeft == 0 || this.scrollLeft == this.maxScrollLeft)) {
e.preventDefault();
e.stopPropagation();
}
if (this.reqId) return;
// 起始值
let scrollLeft = this.$container.scrollLeft;
let left = parseFloat(this.$thumbX.style.left || 0);
let animate = () => {
scrollLeft = easeout(scrollLeft, this.scrollLeft);
left = easeout(left, this.left);
this.$container.scrollLeft = scrollLeft;
this.$thumbX.style.left = left + 'px';
this.innerScroll = true;
if (scrollLeft != this.scrollLeft) {
this.reqId = requestAnimationFrame(animate);
} else {
this.reqId = null;
this.scrollLeft = null;
requestAnimationFrame(() => this.innerScroll = false);
}
};
animate();
}
});
}
/**
* 滚动事件 => 修正滑块位置
*/
bindScroll() {
this.$container.addEventListener('scroll', () => {
if (this.thumbXActive || this.thumbYActive || this.innerScroll) return;
if (this.hasXScrollbar) {
this.$thumbX.style.left = this.$container.scrollLeft / this.maxScrollLeft * this.thumbXMaxLeft + 'px';
}
if (this.hasYScrollbar) {
this.$thumbY.style.top = this.$container.scrollTop / this.maxScrollTop * this.thumbYMaxTop + 'px';
}
});
}
/**
* 观察容器大小
*/
resizeObserver() {
this.$resizeObserver = new ResizeObserver((entries) => {
let contentRect = entries[0].contentRect;
if (!(contentRect.width || contentRect.height)) return;
this.update();
});
this.$resizeObserver.observe(this.$container);
this.$resizeObserver.observe(this.$content);
}
/**
* 使用滚动值修正滑块
* 在 容器大小 或 内容大小 发生改变时调用
*/
update() {
this.setThumbSize();
if (this.hasXScrollbar) {
this.$thumbX.style.left = this.$container.scrollLeft / this.maxScrollLeft * this.thumbXMaxLeft + 'px';
}
if (this.hasYScrollbar) {
this.$thumbY.style.top = this.$container.scrollTop / this.maxScrollTop * this.thumbYMaxTop + 'px';
}
}
/**
* html字符串 转 dom对象
* @param {*} html
* @returns
*/
html2dom(html) {
let element = document.createElement('div');
element.innerHTML = html;
let children = element.children;
if (children.length <= 1) {
return children[0];
} else {
return children;
}
}
/**
* 生成自定义样式
*/
style() {
let content = `
/* 轨道 */
.x-scrollbar__track-x {
height: ${parseInt(this.thumbSize) * 2 + 4}px;
}
.x-scrollbar__track-y {
width: ${parseInt(this.thumbSize) * 2 + 4}px;
}
/* 滑块 */
.x-scrollbar__track-x > .x-scrollbar__thumb-x,
.x-scrollbar__track-y > .x-scrollbar__thumb-y {
background: ${this.thumbBackground};
border-radius: ${parseInt(this.thumbRadius || 0) != 5 ? parseInt(this.thumbRadius || 0) : parseInt(this.thumbSize)}px;
}
.x-scrollbar__track-x > .x-scrollbar__thumb-x {
height: ${parseInt(this.thumbSize)}px;
}
.x-scrollbar__track-y > .x-scrollbar__thumb-y {
width: ${parseInt(this.thumbSize)}px;
}
/* 激活后大小 */
.x-scrollbar__track-x:hover > .x-scrollbar__thumb-x,
.x-scrollbar__track--draging > .x-scrollbar__thumb-x {
height: ${parseInt(this.thumbSize) * 2}px;
}
.x-scrollbar__track-y:hover > .x-scrollbar__thumb-y,
.x-scrollbar__track--draging > .x-scrollbar__thumb-y {
width: ${parseInt(this.thumbSize) * 2}px;
}
/* 鼠标移入轨道 || 拖动过程中 => 显示轨道 & 高亮滑块 */
.x-scrollbar__track-x:hover,
.x-scrollbar__track-y:hover,
.x-scrollbar__track-x.x-scrollbar__track--draging,
.x-scrollbar__track-y.x-scrollbar__track--draging {
background: ${this.trackBackground || 'transparent'};
}`;
this.key = 'x-scrollbar-' + Math.abs(((1 + Math.random()) * Date.now()) | 0).toString(16);
this.$dom.setAttribute(this.key, '');
let style = this.html2dom(`<style ${this.key}></style>`);
content = content.replaceAll('\n.x-scrollbar', `\n[${this.key}] > .x-scrollbar`);
content = content.replaceAll(';', ' !important;');
style.innerHTML = content;
document.querySelector('head').appendChild(style);
}
}
if (typeof exports === 'object' && typeof module !== 'undefined') {
module.exports = XScrollbar;
} else {
window.XScrollbar = XScrollbar;
}
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化