基于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 相关文章推荐
JavaScript面向对象之静态与非静态类
Feb 03 Javascript
用js来定义浏览器中一个左右浮动元素相对于页面主体宽度的位置的函数
Jan 21 Javascript
『jQuery』取指定url格式及分割函数应用
Apr 22 Javascript
jquery操作HTML5 的data-*的用法实例分享
Aug 17 Javascript
限制上传文件大小和格式的jQuery插件实例
Jan 24 Javascript
PHP+mysql+Highcharts生成饼状图
May 04 Javascript
JavaScript提高网站性能优化的建议(二)
Jul 24 Javascript
js实现table添加行tr、删除行tr、清空行tr的简单实例
Oct 15 Javascript
jQuery实现节点的追加、替换、删除、复制功能示例
Jul 11 jQuery
Bootstrap实现的表格合并单元格示例
Feb 06 Javascript
vuejs前后端数据交互之从后端请求数据的实例
Aug 11 Javascript
微信小程序新手教程之页面打开数量限制
Mar 03 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
Zend Framework教程之分发器Zend_Controller_Dispatcher用法详解
2016/03/07 PHP
浅谈php和js中json的编码和解码
2016/10/24 PHP
用PHP做了一个领取优惠券活动的示例代码
2019/07/05 PHP
两个select多选模式的选项相互移动(示例代码)
2014/01/11 Javascript
node.js中的fs.utimesSync方法使用说明
2014/12/15 Javascript
JS创建事件的三种方法(实例代码)
2016/05/12 Javascript
JavaScript通过HTML的class来获取HTML元素的方法总结
2016/05/24 Javascript
JavaScript数据结构中栈的应用之表达式求值问题详解
2017/04/11 Javascript
JS简单实现点击按钮或文字显示遮罩层的方法
2017/04/27 Javascript
详解如何使用PM2将Node.js的集群变得更加容易
2017/11/15 Javascript
Nginx 配置多站点vhost 的方法
2018/01/07 Javascript
vue实现简易的双向数据绑定
2020/12/29 Vue.js
[00:32]DOTA2上海特级锦标赛 Ehome战队宣传片
2016/03/03 DOTA
Python中if __name__ == '__main__'作用解析
2015/06/29 Python
Python利用递归实现文件的复制方法
2018/10/27 Python
详解python websocket获取实时数据的几种常见链接方式
2019/07/01 Python
Python爬取知乎图片代码实现解析
2019/09/17 Python
详解Python中打乱列表顺序random.shuffle()的使用方法
2019/11/11 Python
Django数据结果集序列化并展示实现过程
2020/04/22 Python
五种Python转义表示法
2020/11/27 Python
德国最新街头服饰网上商店:BODYCHECK
2019/09/15 全球购物
Vans(范斯)新西兰官方网站:美国原创极限运动品牌
2020/09/19 全球购物
如何设定的weblogic的热启动模式(开发模式)与产品发布模式
2012/09/08 面试题
静态变量和实例变量的区别
2015/07/07 面试题
英语感恩演讲稿
2014/01/14 职场文书
人力资源管理毕业求职信
2014/08/05 职场文书
关键在于落实心得体会
2014/09/03 职场文书
小学生竞选班干部演讲稿(5篇)
2014/09/12 职场文书
2015年初中元旦晚会活动总结
2014/11/28 职场文书
贷款担保书
2015/01/20 职场文书
草房子读书笔记
2015/06/29 职场文书
小学生读书笔记
2015/07/01 职场文书
2016年10月份红领巾广播稿
2015/12/21 职场文书
银行培训心得体会范文
2016/01/09 职场文书
工作简历的自我评价
2019/05/16 职场文书
goland 设置project gopath的操作
2021/05/06 Golang