30

用JavaScript完成页面自动操作

 4 years ago
source link: http://www.cnblogs.com/strick/p/12149173.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

在之前的一篇《 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);

等到的效果如下图所示。

NN3aUzA.gif

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

二、点击

在触发点击事件时,需要指定一些元素。目前的坐标是随机生成的,每次会遍历元素,当坐标在元素范围内时,才派发事件,完成点击,如下所示。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);

得到的效果如下图所示。

qyAFNjy.gif

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

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

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

demo代码已上传至GitHub:

https://github.com/pwstrick/auto


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK