用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
&n