视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001 知道1 知道21 知道41 知道61 知道81 知道101 知道121 知道141 知道161 知道181 知道201 知道221 知道241 知道261 知道281
问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501
基于casperjs和resemble.js实现一个像素对比服务
2020-11-27 19:34:48 责编:小采
文档
这篇文章主要给大家介绍了关于基于casperjs和resemble.js实现一个像素对比服务的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们来一起学习学习吧。

前言

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

效果预览


前置知识

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

  • casperjs:基于PhantomJS的编写。其内部提供了一个无界面浏览器,简单来说用它你可以以代码的形式来完成模拟人来操作浏览器的操作,其中涉及鼠标各种事件,等等非常多的功能,本次主要使用其附带的截图功能。

  • resemble.js:图片像素对比工具。调用方法简单理解为,传入两张图,返回一张合成图并附带对比参数如差别度等等。基本实现思路可以理解为通过将图片转为canvas后,获取其图像像素点,之后对每个像素点进行一次比对。

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

    整体思路

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

  • 从前端页面接收设计稿图片及需要截取的网站地址与节点信息

  • 将设计稿保存到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

    下载本文
    显示全文
    专题