3年前我不会实现的,现在靠ai辅助实现了

引文

今天大脑闪回以前好像有个需求,就是在一个内容区域内,鼠标移出自定义滚动条隐藏,移入则展示滚动条,当时开发的时候2个思路。1.监听鼠标移入移出来用样式控制原生滚动条的展示,用了display:none/block发现布局抖动,用hover:hidden/overflow也没办法 2.自定义实现一个滚动条去控制显示隐藏,然后没实现出来(菜的抠脚),然而到ai发展的今天发现无论是思路1还是2都让ai实现了一次,而我被ai替代了2次(damn!)

提问:你作为一个前端开发工程师,实现一个自定义滚动条,鼠标移过去滚动条自然展示,鼠标移开滚动条消失,布局不可以变化,用原生js实现下

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Custom Scrollbar</title>
    <style>
        .container {
            width: 500px;
            height: 300px;
            overflow: hidden;
            position: relative;
        }
        
        .content {
            width: 100%;
            height: 100%;
            overflow: auto;
            padding: 10px;
        }
        
        .custom-scrollbar {
            position: absolute;
            top: 0;
            right: 0;
            width: 8px;
            background: transparent;
            z-index: 10;
            transition: opacity 0.3s;
        }
        
        .scrollbar-thumb {
            position: absolute;
            top: 0;
            width: 100%;
            background: #888;
            border-radius: 4px;
            cursor: pointer;
        }
    </style>
</head>
<body>
    <div class="container" id="container">
        <div class="content" id="content">
            <!-- 生成100行示例文本 -->
            <div style="height: 2000px;">
                <h3>自定义滚动条示例</h3>
                <p>鼠标移入右侧区域显示滚动条,移出隐藏</p>
                <p>当前时间:<span id="time"></span></p>
                <!-- 重复内容 -->
                <div style="height: 1500px;">
                    <p>滚动内容区域...</p>
                </div>
            </div>
        </div>
        <div class="custom-scrollbar" id="customScrollbar"></div>
    </div>

    <script>
        class CustomScrollbar {
            constructor(containerId, scrollbarId, contentId) {
                this.container = document.getElementById(containerId);
                this.scrollbar = document.getElementById(scrollbarId);
                this.content = document.getElementById(contentId);
                
                this.isMouseOver = false;
                this.isDragging = false;
                this.scrollPosition = 0;
                
                this.init();
            }
            
            init() {
                this.createScrollbar();
                this.bindEvents();
                this.updateScrollbar();
            }
            
            createScrollbar() {
                const thumb = document.createElement('div');
                thumb.className = 'scrollbar-thumb';
                this.scrollbar.appendChild(thumb);
                this.thumb = thumb;
            }
            
            bindEvents() {
                // 容器鼠标事件
                this.container.addEventListener('mouseenter', () => this.showScrollbar());
                this.container.addEventListener('mouseleave', () => this.hideScrollbar());
                
                // 滚动条鼠标事件
                this.scrollbar.addEventListener('mouseenter', () => this.showScrollbar());
                this.scrollbar.addEventListener('mouseleave', () => this.hideScrollbar());
                
                // 滚动内容事件
                this.content.addEventListener('scroll', () => this.updateScrollbar());
                
                // 滑块拖动事件
                this.thumb.addEventListener('mousedown', (e) => this.startDrag(e));
                document.addEventListener('mousemove', (e) => this.onDrag(e));
                document.addEventListener('mouseup', () => this.stopDrag());
                
                // 滚轮事件
                this.container.addEventListener('wheel', (e) => this.handleWheel(e));
            }
            
            showScrollbar() {
                this.scrollbar.style.opacity = '1';
                this.isMouseOver = true;
            }
            
            hideScrollbar() {
                if (!this.isDragging) {
                    this.scrollbar.style.opacity = '0';
                    this.isMouseOver = false;
                }
            }
            
            updateScrollbar() {
                const contentHeight = this.content.scrollHeight;
                const visibleHeight = this.content.clientHeight;
                const scrollTop = this.content.scrollTop;
                
                // 计算滑块高度
                const thumbHeight = Math.max(20, (visibleHeight / contentHeight) * visibleHeight);
                this.thumb.style.height = `${thumbHeight}px`;
                
                // 计算滑块位置
                const thumbPosition = (scrollTop / (contentHeight - visibleHeight)) * 
                                    (visibleHeight - thumbHeight);
                this.thumb.style.top = `${thumbPosition}px`;
                
                this.scrollPosition = scrollTop;
            }
            
            startDrag(e) {
                e.preventDefault();
                this.isDragging = true;
                this.dragStartY = e.clientY;
                this.dragStartTop = parseInt(this.thumb.style.top) || 0;
                this.scrollbar.style.opacity = '1';
            }
            
            onDrag(e) {
                if (!this.isDragging) return;
                
                const deltaY = e.clientY - this.dragStartY;
                const newTop = this.dragStartTop + deltaY;
                
                // 限制滑块在有效范围内
                const maxTop = this.container.clientHeight - this.thumb.clientHeight;
                const clampedTop = Math.max(0, Math.min(newTop, maxTop));
                
                this.thumb.style.top = `${clampedTop}px`;
                
                // 计算并设置内容滚动位置
                const contentHeight = this.content.scrollHeight;
                const visibleHeight = this.content.clientHeight;
                const scrollPosition = (clampedTop / (maxTop || 1)) * 
                                      (contentHeight - visibleHeight);
                
                this.content.scrollTop = scrollPosition;
                this.scrollPosition = scrollPosition;
            }
            
            stopDrag() {
                this.isDragging = false;
                if (!this.isMouseOver) {
                    this.scrollbar.style.opacity = '0';
                }
            }
            
            handleWheel(e) {
                if (!this.isMouseOver && !this.isDragging) return;
                
                e.preventDefault();
                const delta = e.deltaY > 0 ? 50 : -50;
                this.content.scrollTop += delta;
                this.updateScrollbar();
            }
        }

        // 初始化
        const scrollbar = new CustomScrollbar('container', 'customScrollbar', 'content');
        
        // 更新时间显示
        function updateTime() {
            document.getElementById('time').textContent = new Date().toLocaleTimeString();
        }
        setInterval(updateTime, 1000);
        updateTime();
    </script>
</body>
</html>

进而我在思考能不能抽离成一个js库方便调用这个功能

提问:用js封装成一个库,方便前端调用

js 复制代码
// custom-scrollbar.js
class CustomScrollbar {
    constructor(options) {
        // 合并默认配置和用户配置
        this.options = {
            container: null,
            content: null,
            width: 8,
            thumbColor: '#888',
            thumbHoverColor: '#555',
            thumbBorderRadius: 4,
            autoHide: true,
            hideDelay: 2000,
            ...options
        };

        // 验证必需参数
        if (!this.options.container || !this.options.content) {
            throw new Error('必须提供container和content元素');
        }

        this.container = this.options.container;
        this.content = this.options.content;
        
        this.isMouseOver = false;
        this.isDragging = false;
        this.hideTimer = null;
        
        this.init();
    }

    init() {
        // 确保容器是相对定位
        this.container.style.position = 'relative';
        
        // 创建滚动条轨道
        this.scrollbar = document.createElement('div');
        this.scrollbar.className = 'custom-scrollbar-track';
        this.scrollbar.style.position = 'absolute';
        this.scrollbar.style.top = '0';
        this.scrollbar.style.right = '0';
        this.scrollbar.style.width = `${this.options.width}px`;
        this.scrollbar.style.backgroundColor = 'transparent';
        this.scrollbar.style.zIndex = '10';
        this.scrollbar.style.transition = 'opacity 0.3s';
        this.scrollbar.style.opacity = this.options.autoHide ? '0' : '1';
        
        // 创建滑块
        this.thumb = document.createElement('div');
        this.thumb.className = 'custom-scrollbar-thumb';
        this.thumb.style.position = 'absolute';
        this.thumb.style.top = '0';
        this.thumb.style.width = '100%';
        this.thumb.style.backgroundColor = this.options.thumbColor;
        this.thumb.style.borderRadius = `${this.options.thumbBorderRadius}px`;
        this.thumb.style.cursor = 'pointer';
        
        this.scrollbar.appendChild(this.thumb);
        this.container.appendChild(this.scrollbar);
        
        this.bindEvents();
        this.updateScrollbar();
    }

    bindEvents() {
        // 容器鼠标事件
        this.container.addEventListener('mouseenter', () => this.showScrollbar());
        this.container.addEventListener('mouseleave', () => this.hideScrollbar());
        
        // 滚动条鼠标事件
        this.scrollbar.addEventListener('mouseenter', () => this.showScrollbar());
        this.scrollbar.addEventListener('mouseleave', () => this.hideScrollbar());
        
        // 滚动内容事件
        this.content.addEventListener('scroll', () => this.updateScrollbar());
        
        // 滑块事件
        this.thumb.addEventListener('mousedown', (e) => this.startDrag(e));
        
        // 滚轮事件
        this.container.addEventListener('wheel', (e) => this.handleWheel(e), { passive: false });
    }

    showScrollbar() {
        clearTimeout(this.hideTimer);
        this.hideTimer = null;
        this.scrollbar.style.opacity = '1';
        this.isMouseOver = true;
    }

    hideScrollbar() {
        if (!this.options.autoHide || this.isDragging) return;
        
        this.hideTimer = setTimeout(() => {
            this.scrollbar.style.opacity = '0';
            this.isMouseOver = false;
        }, this.options.hideDelay);
    }

    updateScrollbar() {
        const contentHeight = this.content.scrollHeight;
        const visibleHeight = this.content.clientHeight;
        const scrollTop = this.content.scrollTop;
        
        // 计算滑块高度(最小20px)
        const thumbHeight = Math.max(20, (visibleHeight / contentHeight) * visibleHeight);
        this.thumb.style.height = `${thumbHeight}px`;
        
        // 计算滑块位置
        const maxScroll = contentHeight - visibleHeight;
        const maxThumbPosition = visibleHeight - thumbHeight;
        const thumbPosition = (scrollTop / maxScroll) * maxThumbPosition;
        
        this.thumb.style.top = `${thumbPosition}px`;
    }

    startDrag(e) {
        e.preventDefault();
        this.isDragging = true;
        this.dragStartY = e.clientY;
        this.dragStartTop = parseInt(this.thumb.style.top) || 0;
        this.scrollbar.style.opacity = '1';
        
        // 添加全局事件监听
        document.addEventListener('mousemove', this.onDrag);
        document.addEventListener('mouseup', this.stopDrag);
    }

    onDrag = (e) => {
        if (!this.isDragging) return;
        
        const deltaY = e.clientY - this.dragStartY;
        const newTop = this.dragStartTop + deltaY;
        
        // 限制滑块在有效范围内
        const maxTop = this.content.clientHeight - this.thumb.clientHeight;
        const clampedTop = Math.max(0, Math.min(newTop, maxTop));
        
        this.thumb.style.top = `${clampedTop}px`;
        
        // 计算并设置内容滚动位置
        const contentHeight = this.content.scrollHeight;
        const visibleHeight = this.content.clientHeight;
        const scrollPosition = (clampedTop / maxTop) * (contentHeight - visibleHeight);
        
        this.content.scrollTop = scrollPosition;
    }

    stopDrag = () => {
        this.isDragging = false;
        document.removeEventListener('mousemove', this.onDrag);
        document.removeEventListener('mouseup', this.stopDrag);
        this.hideScrollbar();
    }

    handleWheel(e) {
        if (!this.isMouseOver && !this.isDragging) {
            e.preventDefault();
            return;
        }
        
        e.preventDefault();
        const delta = e.deltaY > 0 ? 50 : -50;
        this.content.scrollTop += delta;
        this.updateScrollbar();
    }

    // 销毁实例,清理DOM和事件监听
    destroy() {
        clearTimeout(this.hideTimer);
        this.scrollbar.remove();
        
        // 移除所有事件监听
        this.container.removeEventListener('mouseenter', this.showScrollbar);
        this.container.removeEventListener('mouseleave', this.hideScrollbar);
        this.scrollbar.removeEventListener('mouseenter', this.showScrollbar);
        this.scrollbar.removeEventListener('mouseleave', this.hideScrollbar);
        this.content.removeEventListener('scroll', this.updateScrollbar);
        this.thumb.removeEventListener('mousedown', this.startDrag);
        this.container.removeEventListener('wheel', this.handleWheel);
        
        document.removeEventListener('mousemove', this.onDrag);
        document.removeEventListener('mouseup', this.stopDrag);
    }
}

// 导出库函数
function initCustomScrollbar(options) {
    return new CustomScrollbar(options);
}

// 导出到全局作用域(UMD模式)
if (typeof module !== 'undefined' && module.exports) {
    module.exports = initCustomScrollbar;
} else if (typeof define === 'function' && define.amd) {
    define([], function() {
        return initCustomScrollbar;
    });
} else {
    window.initCustomScrollbar = initCustomScrollbar;
}

使用方法:

  1. 引入库文件:
xml 复制代码
html
	<script src="path/to/custom-scrollbar.js"></script>
  1. 初始化滚动条:
php 复制代码
javascript
	// 基本用法

	const myScrollbar = initCustomScrollbar({

	    container: document.getElementById('container'),

	    content: document.getElementById('content')

	});

	 

	// 高级用法(带配置)

	const myScrollbar = initCustomScrollbar({

	    container: document.querySelector('.scroll-container'),

	    content: document.querySelector('.scroll-content'),

	    width: 10,

	    thumbColor: '#ff5722',

	    thumbHoverColor: '#e64a19',

	    thumbBorderRadius: 5,

	    autoHide: false,

	    hideDelay: 3000

	});
  1. 销毁实例(当不需要时):
ini 复制代码
javascript
	myScrollbar.destroy();

配置参数:

参数 类型 默认值 说明
container HTMLElement 必填 滚动容器元素
content HTMLElement 必填 滚动内容元素
width number 8 滚动条宽度(像素)
thumbColor string #888 滑块颜色
thumbHoverColor string #555 滑块悬停颜色
thumbBorderRadius number 4 滑块圆角
autoHide boolean true 是否自动隐藏滚动条
hideDelay number 2000 自动隐藏延迟(毫秒)

特点:

  1. 灵活配置:支持高度定制化配置
  2. 内存安全:提供destroy方法清理资源
  3. 兼容性:支持CommonJS、AMD和全局变量三种引入方式
  4. 性能优化:使用passive事件监听提升滚动性能
  5. 平滑动画:使用CSS transition实现平滑的显示/隐藏效果
  6. 响应式:自动适应容器大小变化

示例代码:

html 复制代码
<!DOCTYPE html>
<html>
<head>
    <style>
        .container {
            width: 500px;
            height: 300px;
            position: relative;
            border: 1px solid #ccc;
        }
        .content {
            width: 100%;
            height: 100%;
            padding: 10px;
            overflow: auto;
        }
    </style>
</head>
<body>
    <div class="container" id="container">
        <div class="content" id="content">
            <!-- 长内容区域 -->
            <div style="height: 1500px;">
                <h3>自定义滚动条库示例</h3>
                <p>这是一个使用自定义滚动条库的示例页面</p>
                <!-- 更多内容 -->
            </div>
        </div>
    </div>

    <script src="custom-scrollbar.js"></script>
    <script>
        // 初始化滚动条
        const scrollbar = initCustomScrollbar({
            container: document.getElementById('container'),
            content: document.getElementById('content'),
            width: 12,
            thumbColor: '#4CAF50',
            thumbHoverColor: '#388E3C',
            thumbBorderRadius: 6,
            hideDelay: 3000
        });

        // 窗口大小变化时更新
        window.addEventListener('resize', () => {
            scrollbar.updateScrollbar();
        });
    </script>
</body>
</html>

这个库提供了完整的自定义滚动条功能,支持多种配置选项,并且可以方便地集成到任何前端项目中。通过提供destroy方法,确保可以正确清理资源,避免内存泄漏。

ai的回答不但给了实现还贴心给了文档和示例

然后我在思考用控制原生滚动条的方式,它会如何给我答案(虽然第一次给了我错误的答案,但是第二次询问下)

提问:通过控制原生的滚动条样式实现鼠标移入展示原生滚动条,移出隐藏原生滚动条嘛,原生js和css实现

html 复制代码
<!DOCTYPE html>  
<html lang="zh-CN">  
<head>  
    <meta charset="UTF-8">  
    <meta name="viewport" content="width=device-width, initial-scale=1.0">  
    <title>原生滚动条动态显示/隐藏</title>  
    <style>  
        .container {  
            width: 500px;  
            height: 300px;  
            position: relative;  
            overflow: hidden; /* 默认隐藏滚动条 */  
            box-sizing: border-box;  
            border: 1px solid #ccc;  
            padding-right: 15px; /* 预留滚动条空间 */  
        }  
  
        .content {  
            width: 100%;  
            height: 100%;  
            overflow: auto;  
            padding: 10px;  
        }  
  
        /* Chrome/Safari 隐藏滚动条 */  
        .content::-webkit-scrollbar {  
            display: none;  
        }  
  
        /* 鼠标移入时显示滚动条 */  
        .container:hover .content {  
            overflow-y: auto; /* 显示垂直滚动条 */  
        }  
  
        /* 针对 Chrome/Safari 显示滚动条 */  
        .container:hover .content::-webkit-scrollbar {  
            display: block;  
            width: 15px;  
        }  
  
        /* 滚动条滑块样式 */  
        .container:hover .content::-webkit-scrollbar-thumb {  
            background-color: #888;  
            border-radius: 1px;  
        }  
  
        /* 滚动条轨道样式 */  
        .container:hover .content::-webkit-scrollbar-track {  
            background: transparent;  
        }  
    </style>  
</head>  
<body>  
    <div class="container">  
        <div class="content">  
            <div style="height: 1500px;">  
                <h3>原生滚动条动态控制示例</h3>  
                <p>鼠标移入右侧区域显示滚动条,移出隐藏</p>  
                <p>当前时间:<span id="time"></span></p>  
                <p>滚动内容区域...</p>  
                <div style="height: 1000px;">  
                    <p>更多内容...</p>  
                </div>  
            </div>  
        </div>  
    </div>  
  
    <script>  
        // 更新时间显示  
        function updateTime() {  
            document.getElementById('time').textContent = new Date().toLocaleTimeString();  
        }  
        setInterval(updateTime, 1000);  
        updateTime();  
  
        // 监听容器鼠标事件  
        const container = document.querySelector('.container');  
        const content = document.querySelector('.content');  
  
        container.addEventListener('mouseenter', () => {  
            // 显示滚动条  
            content.style.overflowY = 'auto';  
            // 针对 Chrome/Safari 显示滚动条  
            content.style.webkitOverflowScrolling = 'touch';  
        });  
  
        container.addEventListener('mouseleave', () => {  
            // 隐藏滚动条  
            content.style.overflowY = 'hidden';  
            // 针对 Chrome/Safari 隐藏滚动条  
            content.style.webkitOverflowScrolling = 'auto';  
        });  
    </script>  
</body>  
</html>

总结:

1.现在ai真的非常强大,可以帮你提供思路实现大概逻辑

2.你必须学会如何正确引导ai给你答案,正确的提问

3.君子善假于物也,世界上唯一不变的事情就是世界一直在日新月异的变化

相关推荐
用户904706683574 小时前
commonjs的本质
前端
Zyx20074 小时前
用 JavaScript 打造 AI 大脑:前端开发者的新时代——基于 Brain.js 的浏览器端 NLP 实战
javascript·机器学习
Sailing4 小时前
5分钟搞定 DeepSeek API 配置:从配置到调用一步到位
前端·openai·ai编程
Pa2sw0rd丶4 小时前
如何在 React 中实现键盘快捷键管理器以提升用户体验
前端·react.js
非凡ghost4 小时前
ToDoList(开源待办事项列表) 中文绿色版
前端·javascript·后端
用户6120414922134 小时前
html+layui+node+js做的个人财务管理系统
javascript·后端
j七七4 小时前
5分钟搭微信自动回复机器人5分钟搭微信自动回复机器人
运维·服务器·开发语言·前端·python·微信
快起来别睡了4 小时前
TypeScript装饰器详解:像搭积木一样给代码加功能
前端·typescript
OpenTiny社区5 小时前
如何使用 TinyEditor 快速部署一个协同编辑器?
前端·vue.js