基于casperjs和resemble.js实现一个像素对比服务详解


Posted in Javascript onJanuary 10, 2018

前言

本次分享一个提供设计稿与前端页面进行像素对比的node服务,旨在为测试或者前端人员自己完成一个辅助性测试。相信我,在像素级别的对比下,网页对设计稿的还原程度一下子就会凸显出来。下面话不多说了,来一起看看详细的介绍吧。

效果预览

基于casperjs和resemble.js实现一个像素对比服务详解

前置知识

本次用到了以下两个库作为辅助工具:

  • casperjs:基于PhantomJS的编写。其内部提供了一个无界面浏览器,简单来说用它你可以以代码的形式来完成模拟人来操作浏览器的操作,其中涉及鼠标各种事件,等等非常多的功能,本次主要使用其附带的截图功能。
  • resemble.js:图片像素对比工具。调用方法简单理解为,传入两张图,返回一张合成图并附带对比参数如差别度等等。基本实现思路可以理解为通过将图片转为canvas后,获取其图像像素点,之后对每个像素点进行一次比对。

所以整个服务我们应该已经有了大题的思路即通过casperjs来进入某个网站截取某个页面,再将其与设计图进行比对得出结果。

整体思路

基于casperjs和resemble.js实现一个像素对比服务详解

通过上图我们应该能整理出一个大概的流程:

  • 从前端页面接收设计稿图片及需要截取的网站地址与节点信息
  • 将设计稿保存到images文件夹
  • 开启子进程,启动casperjs,完成对目标网站的截取
  • 截取后请求form.html将图片地址信息填入并重新传回服务器
  • 服务端获取图片信息通过resemblejs将截取图与设计稿进行比对
  • 结果传回前端页面

这其中有一个问题可能会有人注意到就是:为什么在casperjs中对目标网站截图了不能直接把信息传回服务器中,而是选择了再去打开一个表单页面通过表单的形式来提交信息?

答:首先我对casperjs和node了解都不那么深入,我理解的是首先casperjs不是一个node模块,它是跑在操作系统中的,我尚且没有发现怎么在casperjs中建立与node服务的通信,如果有方法一定要告诉我,因为我真的不太了解casper!其次由于无法建立通信,我只能退而求其次,通过casper快速打开一个我写好的表单页面并且填写好图片信息传回服务器,这么做是可以完成最初的诉求。所以就有了上面from.html那段的操作。

实现细节

实现一个简易静态服务器

因为涉及到index.html与form.html页面的返回,故需要实现一个超级简易的静态服务器。代码如下:

const MIME_TYPE = {
 "css": "text/css",
 "gif": "image/gif",
 "html": "text/html",
 "ico": "image/x-icon",
 "jpeg": "image/jpeg",
 "jpg": "image/jpg",
 "js": "text/javascript",
 "json": "application/json",
 "pdf": "application/pdf",
 "png": "image/png",
 "svg": "image/svg+xml",
 "swf": "application/x-shockwave-flash",
 "tiff": "image/tiff",
 "txt": "text/plain",
 "wav": "audio/x-wav",
 "wma": "audio/x-ms-wma",
 "wmv": "video/x-ms-wmv",
 "xml": "text/xml"
}
function sendFile(filePath, res) {
 fs.open(filePath, 'r+', function(err){ //根据路径打开文件
  if(err){
   send404(res)
  }else{
   let ext = path.extname(filePath)
   ext = ext ? ext.slice(1) : 'unknown'
   let contentType = MIME_TYPE[ext] || "text/plain" //匹配文件类型
   fs.readFile(filePath,function(err,data){
    if(err){
     send500(res)
    }else{
     res.writeHead(200,{'content-type':contentType})
     res.end(data)
    }
   })
  }
 })
}

解析表单并将图片存储到images文件夹

const multiparty = require('multiparty') //解析表单
let form = new multiparty.Form()
 form.parse(req, function (err, fields, files) {
  let filename = files['file'][0].originalFilename,
   targetPath = __dirname + '/images/' + filename,
  if(filename){
   fs.createReadStream(files['file'][0].path).pipe(fs.createWriteStream(targetPath))
   ...
  } 
 })

通过创建可读流读出文件内容,再通过pipe写入到制定路径下即可保存上传来的图片。

运行casperjs

const { spawn } = require('child_process')
spawn('casperjs', ['casper.js', filename, captureUrl, selector, id])
casperjs.stdout.on('data', (data) => {
 ...
})

通过spawn可以创建子进程来启动casperjs,同样也可以使用exec等。

截图并提交数据到form.html

const system = require('system')
const host = 'http://10.2.45.110:3033'
const casper = require('casper').create({
 // 浏览器窗口大小
 viewportSize: {
  width: 1920,
  height: 4080
 }
})
const fileName = decodeURIComponent(system.args[4])
const url = decodeURIComponent(system.args[5])
const selector = decodeURIComponent(system.args[6])
const id = decodeURIComponent(system.args[7])
const time = new Date().getTime()
casper.start(url)
casper.then(function() {
  console.log('正在截图请稍后')
  this.captureSelector('./images/casper'+ id + time +'.png', selector)
})
casper.then(function() {
 casper.start(host + '/form.html', function() {
  this.fill('form#contact-form', {
   'diff': './images/casper'+ id + time +'.png',
   'point': './images/' + fileName,
   'id': id
  }, true)
 })
})
casper.run()

代码还是比较简单的,主要过程就是打开一个页面,然后在then中传入你的操作,最后执行run。在这个过程里我不太知道如何与node服务通信,故选择了再开一个页面。。想深入研究的可以去看casperjs的官网非常详尽!

通过resemble.js进行像素比对并返回数据

function complete(data) {
  let imgName = 'diff'+ new Date().getTime() +'.png',
   imgUrl,
   analysisTime = data.analysisTime,
   misMatchPercentage = data.misMatchPercentage,
   resultUrl = './images/' + imgName
  fs.writeFileSync(resultUrl, data.getBuffer())
  imgObj = {
   ...
  }
  let resEnd = resObj[id] // 找回最开始的res返回给页面数据
  resEnd.writeHead(200, {'Content-type':'application/json'})
  resEnd.end(JSON.stringify(imgObj))
 }
let result = resemble(diff).compareTo(point).ignoreColors().onComplete(complete)

这其中涉及到了一个点,即我现在所得到的结果要返回给最初的请求里,而从一开始的请求到现在我已经中转了多次,导致我现在找不到我最初的返回体res了。想了很久只能暂时采用了设定全局对象,在接收最初的请求后将请求者的ip和时间戳设定为唯一id存为该对象的key,value为当前的res。同时整个中转流程中时刻传递id,最后通过调用resObj[id]来得到一开始的返回体,返回数据。这个方法我不认为是最优解,但是鉴于我现在想不出来好方法为了跑通整个服务不得已。。如果有新的思路请务必告知!!

部署

安装PhantomJS(osx)

官网下载: phantomjs-2.1.1-macosx.zip

解压路径:/User/xxx/phantomjs-2.1.1-macosx

添加环境变量:~/.bash_profile 文件中添加

export PATH="$PATH:/Users/xxx/phantomjs-2.1.1-macosx/bin"

terminal输入:phantomjs --version

能看到版本号即安装成功

安装casperjs

brew update && brew install casperjs

安装resemble.js

cnpm i resemblejs //已写进packjson可不用安装
brew install pkg-config cairo libpng jpeg giflib
cnpm i canvas //node内运行canvas

node服务

git clone https://github.com/Aaaaaaaty/gui-auto-test.git
cd gui-auto-test
cnpm i
cd pxdiff
nodemon server.js

打开http://localhost:3033/index.html

惯例po作者的博客,不定时更新中——

参考文献

  • PhantomJS 安装
  • casperjs 文档
  • resemble.js 文档

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
jQuery之end()和pushStack()使用介绍
Feb 07 Javascript
JavaScript 模式之工厂模式(Factory)应用介绍
Nov 15 Javascript
jquery图片放大功能简单实现
Aug 01 Javascript
jQuery移动页面开发中的触摸事件与虚拟鼠标事件简介
Dec 03 Javascript
全面解析Bootstrap布局组件应用
Feb 22 Javascript
JavaScript中文件上传API详解
Apr 01 Javascript
防止重复发送 Ajax 请求
Feb 15 Javascript
AngularJS $http模块POST请求实现
Apr 08 Javascript
node thread.sleep实现示例
Jun 20 Javascript
JavaScript控制浏览器全屏显示简单示例
Jul 05 Javascript
Vue2.x Todo之自定义指令实现自动聚焦的方法
Jan 08 Javascript
Vue中watch、computed、updated三者的区别及用法
Jul 27 Javascript
JavaScript实现快速排序的方法分析
Jan 10 #Javascript
jQuery第一次运行页面默认触发点击事件的实例
Jan 10 #jQuery
js推箱子小游戏步骤代码解析
Jan 10 #Javascript
vue select二级联动第二级默认选中第一个option值的实例
Jan 10 #Javascript
AngularJS使用ui-route实现多层嵌套路由的示例
Jan 10 #Javascript
Vue+jquery实现表格指定列的文字收缩的示例代码
Jan 09 #jQuery
基于Vue、Vuex、Vue-router实现的购物商城(原生切换动画)效果
Jan 09 #Javascript
You might like
上海无线电三厂简史修改版
2021/03/01 无线电
用ODBC的分页显示
2006/10/09 PHP
Mootools 1.2教程 选项卡效果(Tabs)
2009/09/15 Javascript
显示js对象所有属性和方法的函数
2009/10/16 Javascript
Node.js中安全调用系统命令的方法(避免注入安全漏洞)
2014/12/05 Javascript
nodejs开发微博实例
2015/03/25 NodeJs
JS简单实现String转Date的方法
2016/03/02 Javascript
用React实现一个完整的TodoList的示例代码
2017/10/30 Javascript
详解使用vuex进行菜单管理
2017/12/21 Javascript
es6数值的扩展方法
2019/03/11 Javascript
详解Vue之事件处理
2020/07/10 Javascript
Javascript实现打鼓效果
2021/01/29 Javascript
[01:24:09]Ti4 冒泡赛第二轮DK vs C9 1
2014/07/14 DOTA
Python中的fileinput模块的简单实用示例
2015/07/09 Python
python中pygame针对游戏窗口的显示方法实例分析(附源码)
2015/11/11 Python
详解Python使用simplejson模块解析JSON的方法
2016/03/24 Python
Java多线程编程中ThreadLocal类的用法及深入
2016/06/21 Python
Django卸载之后重新安装的方法
2017/03/15 Python
django文档学习之applications使用详解
2018/01/29 Python
python 重命名轴索引的方法
2018/11/10 Python
Python中的取模运算方法
2018/11/10 Python
Python设计模式之职责链模式原理与用法实例分析
2019/01/11 Python
详解用python自制微信机器人,定时发送天气预报
2019/03/25 Python
使用PyOpenGL绘制三维坐标系实例
2019/12/24 Python
Python基于network模块制作电影人物关系图
2020/06/19 Python
Python生成器generator原理及用法解析
2020/07/20 Python
使用jTopo给Html5 Canva中绘制的元素添加鼠标事件
2014/05/15 HTML / CSS
简述安装Slackware Linux系统的过程
2012/01/12 面试题
师德师风承诺书
2014/05/23 职场文书
外贸业务员求职信
2014/06/16 职场文书
银行党的群众路线教育实践活动对照检查材料
2014/09/25 职场文书
2014年班主任德育工作总结
2014/12/05 职场文书
2014年前台接待工作总结
2014/12/05 职场文书
活动总结模板大全
2015/05/11 职场文书
mybatis调用sqlserver存储过程返回结果集的方法
2021/05/08 SQL Server
Python内置数据类型中的集合详解
2022/03/18 Python