如何使用javascript模拟鼠标滚动事件?我在自动化测试中的实践与坑

361 阅读6分钟

最近在做一些自动化测试的时候,遇到了一个需求:需要模拟用户在浏览网页时的滚动操作。我的目标是通过 JavaScript 模拟鼠标滚动事件,从而触发页面上的懒加载或滚动监听器。我发现页面的内容区域是通过一个有固定高度的容器滚动的,window.scrollTop 完全没有任何反应。我才意识到,不能总是监听 window 的滚动,很多情况下我们监听的是某个具体的元素。又引发了一些新的问题,能不能触发事件,但是不滚动元素?如何判断是否已经滚动到了页面的最底部?如何判断一个元素是否可以滚动呢?

场景一:直接调用滚动API,实现页面或元素的实际滚动

在我进行自动化测试或实现某些交互效果时,最常用、最直接的做法就是调用浏览器提供的滚动API,比如 scrollByscrollToscrollIntoView。这些 API 无需模拟用户行为,直接改变页面或元素的滚动位置,具备良好的兼容性和控制力。

下面是我经常用的一个简单例子,用于让页面平滑滚动:

const deltaY = 100;
window.scrollBy({
  top: deltaY,
  left: 0,
  behavior: 'smooth' // 可选参数,实现平滑滚动
});

这个方法的原理很简单:直接改变页面或元素的滚动位置,适合控制页面滚动条、实现锚点跳转、滚动动画等需求。也可以替换掉 window,针对具体的 DOM 元素进行滚动:

const container = document.getElementById('scrollableContainer');
container.scrollTop += 100;

但是,会不会触发 scroll 事件?:

这点经常被误解。其实只要滚动位置发生了变化(即 scrollTop 或 scrollLeft 改变),scroll 事件就会触发,无论是用户滚动还是代码控制。

📚 官方说明:

  • MDN - Element.scrollTo():“This method triggers the scroll event if the element’s scroll position changes.”
  • MDN - scroll event:“The scroll event is fired when the document view or an element has been scrolled.”

但是有时候会在调用滚动 API 后未看到事件触发,我给大家一些思考的方向:

  • scroll 事件绑定的元素跟实际触发滚动的元素不是同一个元素
  • 元素本身没有实际发生滚动;
  • 滚动事件被节流(debounce/throttle)过滤了;
  • 懒加载逻辑使用的是 IntersectionObserver 而不是 scroll

这就引出了我思考的问题:如果我只是想触发滚动监听器,而不是实际滚动页面,应该怎么做?


场景二:模拟滚动事件,触发监听器但不移动页面

后来我发现,在某些测试场景下,我们只需要“让代码以为用户滚动了”,并不要求实际滚动页面。比如测试埋点逻辑是否触发、验证动画联动是否响应,或者检查某些组件在监听滚动时的表现。这种情况下,用事件模拟是更优雅的选择。

我们可以通过构造 WheelEvent 的方式,来模拟鼠标滚动操作,并显式触发:

function simulateMouseScroll(element, deltaY) {
    // 创建一个模拟的WheelEvent
    var event = new WheelEvent('wheel', {
        deltaY: deltaY, // 正数表示向下滚动,负数表示向上滚动
        bubbles: true, // 事件是否应该冒泡
        cancelable: true, // 事件是否可以取消
    });

    // 触发这个事件
    element.dispatchEvent(event);
}

// 使用示例
var myElement = document.getElementById('yourElementId');
simulateMouseScroll(myElement, 100); // 模拟向下滚动100个

注意:

模拟 WheelEvent 只会触发 wheel 事件监听器,不会自动引起元素滚动,也不会触发 scroll 事件,除非你在 wheel 的处理器中显式调用了 scrollTo / scrollBy 等方法。

📚 官方说明:

MDN - WheelEvent :“Dispatching a WheelEvent manually does not automatically scroll the page unless your code explicitly does so.”

在现代浏览器中,为了防止脚本模拟用户行为带来安全问题,WheelEvent 可能被限制,无法完全模拟真实的滚动行为。因为这些事件通常由用户交互触发,且浏览器的安全策略可能会阻止非用户触发的这类事件。

请注意,由于安全策略和浏览器差异,这种方法可能不会在所有场景下都能按预期工作,尤其是当涉及到跨域iframe或受到严格安全策略限制的环境。在实际应用中,应当首先考虑使用元素的滚动属性和方法来直接改变滚动位置。


两种滚动模拟方式,选哪种?

  • 想让页面真的滚动,就用原生滚动 API(推荐)。
  • 只想触发 wheel 相关逻辑(如交互响应、监听测试),可以模拟事件。
  • 注意:懒加载通常依赖的是 IntersectionObserver,而不是 scroll 事件。

滚动相关的常见判断逻辑:我怎么判断滚动到底了?

另一个让我经常用到的逻辑是:页面是否滚动到底了?

最常见的场景是懒加载、无限滚动、滚动触底提示等。我的实现是比较常规的三段式判断:当前滚动距离 + 可视区域高度 与页面总高度的对比。

function isBottomReached() {
    // 获取窗口的滚动位置
    var scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;

    // 获取整个文档的高度(可视区域加上滚动出去的部分)
    var docHeight = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);

    // 获取可视窗口的高度
    var winHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;

    // 判断是否滚动到底部
    return scrollTop + winHeight >= docHeight;
}

配合滚动事件监听器使用,就能实现动态判断了:

window.addEventListener("scroll", function() {
    if (isBottomReached()) {
        console.log("滚动到了页面最底部!");
        // 可以在这里触发懒加载、打点等逻辑
    }
});

怎么判断某个元素是否支持滚动?

还有一个需求也很常见——我需要判断一个元素是否具有滚动能力。这个判断在做组件封装时尤其有用,比如判断内容区域是否超出容器、是否该展示滚动条或加载更多。

以下是我自己封装的判断函数:

function canElementScroll(selector) {
    // 根据传入的selector(类名或ID)查找DOM元素
    const element = document.querySelector(selector);

    // 检查是否找到了元素
    if (!element) {
        console.error(`无法找到匹配的元素:${selector}`);
        return false;
    }

    // 检查元素是否可以在水平方向上滚动
    const canScrollHorizontally = element.scrollWidth > element.clientWidth;

    // 检查元素是否可以在垂直方向上滚动
    const canScrollVertically = element.scrollHeight > element.clientHeight;

    // 如果元素可以在任一方向上滚动,则返回true
    return canScrollHorizontally || canScrollVertically;
}

// 使用示例
// 通过类名判断
const canScrollByClass = canElementScroll('.scrollable-class');
if (canScrollByClass) {
    console.log('通过类名找到的元素支持滚动');
} else {
    console.log('通过类名找到的元素不支持滚动');
}

// 通过ID判断
const canScrollById = canElementScroll('#scrollable-id');
if (canScrollById) {
    console.log('通过ID找到的元素支持滚动');
} else {
    console.log('通过ID找到的元素不支持滚动');
}

你可以传入 .class#id 来判断任意元素的滚动能力。