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