• 用JavaScript完成页面自动操作


      在之前的一篇《JavaScript实现按键精灵》中曾记录了几个事件对象,本文将会对它们进行一次实战,要完成的动作包括滚动、点击和翻页。

    一、滚动

      滚动是通过修改容器元素的scrollTop属性实现的,期间会进行一系列的计算,而每次滚动都会包含一个个小的偏移动作,为了让这些动作能有序进行,自定义了一个Promise,如下所示。

    /**
     * 简易Promise
     */
    var Promise = {
      fns: [],
      then: function(fn) {
        this.fns.push(fn);
        return this;
      },
      resolve: function() {
        if (this.fns.length == 0) return;
        var fn = this.fns.splice(0, 1);
        fn[0] && fn[0].call(this);
      }
    };

      为了让滚动表现的更加顺滑,采用了requestAnimationFrame()方法,滚动的方向分为三种,分别是向上、向下或待机,如下所示。

    /**
     * 随机整数
     */
    var Util = {
      random: function(max) {
        return Math.floor(Math.random() * max);
      }
    };
    /**
     * 随机滚动
     * container 容器元素
     */
    function scrollTopBottom(container) {
      var num = Util.random(10);
      for (var i = 0; i < num; i++) {
        (function(count) {
          Promise.then(function() {
            var direction,      //滚动方向
              destination,      //滚动的目标位置
              current,          //当前滚动距离
              slide = 0;        //滚动距离
            destination = Util.random(2000);    
            current = container.scrollTop;        
            direction = Util.random(3);
            (function moveInner() {
              switch (direction) {
                case 0:             //向上滚动
                  current += 10;
                  break;
                case 1:             //向下滚动
                  current -= 10;
                  if (current < 0) current = 0;
                  break;
                default:            //保持原地
                  break;
              }
              slide += 10;          //执行滚动
              console.log(count, slide, current, destination);
              container.scrollTop = current;
              if (slide <= destination && current > 0) {
                window.requestAnimationFrame(moveInner);    //顺滑的滚动
              } else {
                Promise.resolve();                          //执行下一个动作
              }
            })();
          });
        })(i);
      }
      Promise.resolve();        //开始滚动
    }

      滚动的容器元素多变,可能是body,也可能是根元素或者是其它元素,具体得视页面而定,示例页面采用的是根元素,如下所示。

    /**
     * document.documentElement
     * document.body
     */
    scrollTopBottom(document.documentElement);

      等到的效果如下图所示。

      虽然完成了自动滚动,但当前的代码无法精度控制,例如难以配置成第几秒向上或向下滚动,或者指定滚动到真实用户会停留的位置的时间。

    二、点击

      在触发点击事件时,需要指定一些元素。目前的坐标是随机生成的,每次会遍历元素,当坐标在元素范围内时,才派发事件,完成点击,如下所示。MouseEvent中的clientX、pageX等属性可参考《触屏touch事件记录》中的记录。

    /**
     * 点击
     */
    function click() {
      var links = document.querySelectorAll("img"),     //指定要触发的元素
        x = Util.random(window.outerWidth),             //随机X坐标
        y = Util.random(document.body.scrollHeight),    //随机Y坐标
        clientY = y > window.outerHeight ? (y - window.outerHeight) : y;
      var event = new MouseEvent("click", {
        bubbles: true,       //能够冒泡
        cancelable: true,    //可以取消事件
        view: window,        //窗口
        clientX: x,
        clientY: clientY,    //相对于视口的垂直偏移
        pageX: x,
        pageY: y             //包含垂直滚动的偏移
      });
      [].forEach.call(links, function(value, key) {
        var rect = value.getBoundingClientRect();
        //判断当前坐标是否在元素范围内
        if(x >= rect.left && x<=rect.right && y>=rect.top && y<=rect.bottom) {
          console.log(x, y);
          value.dispatchEvent(event);        //派发事件
        }
      });
    }
    function runClick() {
      for (var j = 0; j < 50; j++) {
        (function(j) {
          setTimeout(function() {
            click();         //随意点击页面
          }, 2000 * j);      //不集中在一个时间执行
        })(j);
      }
    }

      虽然完成了自动点击,但还不够灵活。当要触发的动作不是由指定的元素触发的时,这段脚本就起不了作用,并且手机屏幕尺寸众多,难以精确的在某一指定区域内点击。

      由于很依赖事件类型,因此当绑定的动作不在该事件中时,代码也会失效。如果要触发页面监测的请求,那么不得不先去翻源码,搜寻触发事件。

    三、翻页

      现在很多活动页面都是以全屏翻页的形式出现,通过touchstart、touchmove和touchend三个事件,就能模拟出手指滑动的效果,方向既可以是从下到上,也可以是从右往左,下面的代码采用了前者。使用柯里化的方式减少了touch()函数的参数,TouchEvent中的touches和targetTouches参数,也可以参考《触屏touch事件记录》中的记录。

    /**
     * 竖屏翻页
     */
    var identifier = 0,
      eventType = ["touchstart", "touchmove", "touchend"];
    function slide(container) {
      var x = Util.random(window.outerWidth),
        y = 300,
        currying = touch(container, x, y);
      currying(eventType[0]);
      currying(eventType[1]);
      currying(eventType[2]);
    }
    function touch(container, x, y) {
      var interval = 100;                 //滑动距离
      return function(type) {
        identifier++;
        if (type == eventType[1]) {       //touchmove事件更改Y坐标
          y -= interval;
        }
        var t = new Touch({
          identifier: identifier,
          target: container,
          clientX: x,
          clientY: y,
          pageX: x,
          pageY: y
        });
        console.log(`${identifier}, ${type} x: ${x}, y: ${y}`);
        var event = new TouchEvent(type, {
          touches: [t],
          targetTouches: [t]
        });
        container.dispatchEvent(event);
      };
    }
    /**
     * 假设滑动插件是swiper.js
     * 那么取其容器作为slide()的参数传入
     */
    setInterval(function() {
      slide(document.querySelector(".swiper-container"));
    }, 2000);

      得到的效果如下图所示。

      

      示例中使用的是swiper触屏滑动插件,当换成其他插件时,容器就需要跟着改变。

      上述所有自动化操作都是基于DOM结构完成的,水能载舟亦能覆舟,无法跳出DOM的限制,就会导致一系列的问题,例如针对不同页面的结构要做单独的分析、动作精度难以控制、真实的用户轨迹难以模拟、代码不够灵活难以复用。

      在实际情况中,还有很多复杂的动作(例如答题、填表单等),光靠上述这点代码是远远不够的,目前还做不到像按键精灵那样在屏幕上录制行为,这么简洁。

    demo代码已上传至GitHub:

    https://github.com/pwstrick/auto

  • 相关阅读:
    linux 安装Python3
    MYSQL 字符集设置(终端的字符集)
    Linux LVM Logical Volume Management 逻辑卷的管理
    oracle 重命名和重定位数据文件(Oracle Renaming and Relocating Datafiles)
    Azkaban编译
    基于hive的transform实现自定义分隔符数据导出
    MapReduce优化设置
    hive.groupby.skewindata环境变量与负载均衡
    hive的基本操作
    Shell 数组的定义和使用
  • 原文地址:https://www.cnblogs.com/strick/p/12149173.html
Copyright © 2020-2023  润新知