在Sketch的Artboard中插入網頁截圖:javascript
1.一、輸入網址,自動截圖到Artboard中,並居中顯示;html
1.二、可截取網頁局部圖片前端
技術的選型主要是針對截圖功能的選型,插件技術選用sketch-webview-kit。java
截圖技術主要有phantomjs、puppeteer、html2canvas等技術能夠實現截圖功能。node
phantomjs、puppeteer是 headless 瀏覽器技術,puppeteer依賴於node,它們的主要區別以下:
linux
html2canvas能夠經過獲取HTML的某個元素,而後生成Canvas,能讓用戶保存爲圖片。ios
經過需求分析,puppeteer更適合需求,headless + 部分截圖,且node的環境更符合前端技術。git
肯定使用puppeteer構建一個截圖的node服務。github
node服務框架採用eggjs。egg.js是阿里推出的基於koa的node開發框架,可爲截圖提供提供穩定的node服務。web
構建node基礎的框架egg-common-service,在egg-common-service的基礎上提供Screenshot截圖服務。
Sketch Plugin調取Screenshot截圖服務,將web page的截圖插入到sketch中。
用戶在Sketch中發起Screenshot指令;
在Sketch WebView界面中輸入截圖須要的信息,向egg-common-service 發起Screenshot截圖服務請求;
egg-common-service Screenshot服務返回截圖的base64信息給Sketch WebView;
Sketch WebView將圖片base64信息傳遞給Sketch Plugin;
Sketch Plugin將base64圖片繪製在Sketch Artboard中。
交互設計的主要在WebView部分,詳細的設計以下:
let win = new BrowserWindow({ width: 408, height: 356, title:"Web Screen Shot", resizable:false, minimizable:false, maximizable:false, closable:true }); win.on('closed', () => { win = null }); const Panel = `http://localhost:8000/screenshot.html#${Math.random()}`; win.loadURL(Panel); const closeWin = () =>{ win.destroy(); win.close(); }; var contents = win.webContents; //監聽webview的事件:webview->plugin contents.on('fromwebview', function(data) { getImageFrame(data);//在ArtBoard中返回回來的base64圖片 sketch.UI.message("Successfully screenshot and insert into Artboard!"); closeWin(); }); contents.on('closed', function(s) { closeWin(); });
使用axios進行數據處理:
安裝axios:
$ npm install axios
使用:
const axios = require('axios'); axios.get('/user', { params: { ID: 12345 } }) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); }) .then(function () { // always executed }); axios.post('/user', { firstName: 'Fred', lastName: 'Flintstone' }) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); });
主要功能代碼:
<Spin spinning={spinning} tip="In the screenshot, it takes some time..."> <div className={styles.body}> <div className={styles.url}> <span className={styles.itemName}>ArtBoard Name:</span><Input size={size} className={styles.urlInputCss} value={artBoardName} onChange={this.artBoardNameChange} placeholder={artBoardNamePlaceholder} onBlur={this.artBoardNameOnBulr}/> </div> <div className={styles.url}> <span className={styles.itemName}>Page Url:</span><Input size={size} className={styles.urlInputCss} value={url} onChange={this.urlChange} placeholder={urlPlaceholder}/> </div> <div className={styles.line}></div> <div className={styles.part}> <span className={styles.itemName}><Checkbox size={size} className={styles.checkbox} defaultChecked={false} checked={isPart} onChange={this.partChange} disabled={checkboxDisabled}></Checkbox>Page Part:</span> <span className={styles.partTips}>get part of page</span> <div className={styles.partPannel}> <RadioGroup onChange={this.onRadioChange} value={this.state.radioType} disabled={radioDisabled}> <Radio className={styles.radioStyle} value={1} defaultChecked={true}> <span className={styles.radioName}>Default:</span> <Dropdown.Button overlay={menu} size={size} disabled={dropdownDisabled}> {partTypeDefalt.githubcommits.name} </Dropdown.Button></Radio> <Radio className={styles.radioStyle} value={2}> <span className={styles.radioName}>Custom:</span> <Input addonBefore="." size={size} className={styles.urlInputCss} value={partId} onChange={this.partIdChange} placeholder={partIdPlaceholder} disabled={partIdDisabled}/></Radio> <div className={partIdDisabled?styles.partIdTips:styles.partIdTipsLight}>the class name of the part</div> </RadioGroup> </div> <div className={styles.line1}></div> <div className={styles.buttons}> <Button size={size} onClick={this.onCancel} className={styles.button}>{cancel}</Button> <Button size={size} onClick={this.insertPage} type="primary" disabled={buttonDisabled}>{button}</Button> </div> </div> </div> </Spin>
1)、目錄結構
2)、跨配置
使用egg-cors插件,配置以下:
config/plugin.js
exports.cors = { enable: true, package: 'egg-cors' };
'use strict'; module.exports = appInfo => { const config = exports = {} // use for cookie sign key, should change to your own and keep security config.keys = appInfo.name + '_1513779989145_1674' // add your config here // 加載 errorHandler 中間件 config.middleware = [ 'errorHandler' ] // 只對 /api 前綴的 url 路徑生效 // config.errorHandler = { // match: '/api', // } config.rpc = { // registry: { // address: '127.0.0.1:2181', // }, // client: {}, // server: {}, }; config.security = { csrf: { enable: false, }, domainWhiteList: [ 'http://localhost:8000' ], } config.cors = { origin: '*', allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH' }; config.multipart = { fileExtensions: [ '.apk', '.pptx', '.docx', '.csv', '.doc', '.ppt', '.pdf', '.pages', '.wav', '.mov' ], // 增長對 .apk 擴展名的支持 } return config }
-get請求
let query = this.ctx.query; let name = query.name; let id = query.id;
let query = this.ctx.request.body; let name = query.name; let id = query.id;
this.ctx.body = { code: 0, data: '返回的數據', msg: '錯誤數據' }
1)、快速生成項目
$ npm i egg-init -g $ egg-init egg-common-service --type=simple $ cd egg-common-service $ npm i
$ npm run dev
$ open localhost:700
使用VS Code開發和調試。
1)、調試配置,在egg-common-service根目錄下添加.vscode文件夾,向.vscode中添加launch.json,launch.json內容以下:
{ // 使用 IntelliSense 瞭解相關屬性。 // 懸停以查看現有屬性的描述。 // 欲瞭解更多信息,請訪問: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "type": "node", "request": "launch", "name": "Egg Debug", "runtimeExecutable": "npm", "runtimeArgs": [ "run", "debug" ], "console": "integratedTerminal", "restart": true, "protocol": "auto", "port": 9999 }, { "type": "node", "request": "launch", "name": "Egg Debug with brk", "runtimeExecutable": "npm", "runtimeArgs": [ "run", "debug", "--", "--inspect-brk" ], "protocol": "inspector", "port": 9229 }, { "type": "node", "request": "launch", "name": "Egg Test", "runtimeExecutable": "npm", "runtimeArgs": [ "run", "test-local", "--", "--inspect-brk" ], "protocol": "auto", "port": 9229 }, { "type": "node", "request": "attach", "name": "Egg Attach to remote", "localRoot": "${workspaceRoot}", "remoteRoot": "/usr/src/app", "address": "localhost", "protocol": "auto", "port": 9999 } ] }
2)、依次點擊,進入調試狀態
Puppeteer 是一個經過 DevTools Protocol 控制 headless chrome 的 high-level Node 庫,也能夠經過設置使用 非 headless Chrome。
咱們手工能夠在瀏覽器上作的事情 Puppeteer 都能勝任:
1)、生成網頁截圖或者 PDF
2)、爬取大量異步渲染內容的網頁,基本就是人肉爬蟲
3)、模擬鍵盤輸入、表單自動提交、UI 自動化測試
官方提供了一個 playground,能夠快速體驗一下。關於其具體使用不在贅述,官網的 demo 足矣讓徹底不瞭解的同窗入門:
const puppeteer = require('puppeteer'); (async () => { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto('https://example.com'); await page.screenshot({path: 'example.png'}); await browser.close(); })();
Puppeteer有Puppeteer與Puppeteer-Core二個版本,兩者區別:
1).Puppeteer-Core在安裝時不會自動下載 Chromium
2).Puppeteer-Core忽略全部的PUPPETEER_* env 變量.
使用npm安裝:
npm i puppeteer or puppeteer-core
http://www.javashuo.com/article/p-wsxolrzt-eb.html
http://www.mamicode.com/info-detail-2302923.html
https://blog.csdn.net/asas1314/article/details/81633423
https://www.jianshu.com/p/8e65fdcb6d85
1)、pupper下載了一個Chromium,但並無把依賴都裝好。因而要本身把so都裝好。
官方給的是Ubuntu版本的各個so包的apt-get安裝方式,centos版本竟然沒有放!可是仍是有人給出了centos的庫名:
#依賴庫 yum install pango.x86_64 libXcomposite.x86_64 libXcursor.x86_64 libXdamage.x86_64 libXext.x86_64 libXi.x86_64 libXtst.x86_64 cups-libs.x86_64 libXScrnSaver.x86_64 libXrandr.x86_64 GConf2.x86_64 alsa-lib.x86_64 atk.x86_64 gtk3.x86_64 -y #字體 yum install ipa-gothic-fonts xorg-x11-fonts-100dpi xorg-x11-fonts-75dpi xorg-x11-utils xorg-x11-fonts-cyrillic xorg-x11-fonts-Type1 xorg-x11-fonts-misc -y
修改啓動瀏覽器的代碼,加上args:
const puppeteer = require('puppeteer'); (async () => { const browser = await puppeteer.launch({args: ['--no-sandbox', '--disable-setuid-sandbox']}); const page = await browser.newPage(); await page.goto('https://example.com'); await page.screenshot({path: 'example.png'}); await browser.close(); })();
3)、Macaca-puppeteer
阿里的Macaca也順勢寫了Macaca-puppeteer,能夠在Macaca上直接寫通用的測試用例,在開發機上用圖形界面看效果,上服務器走生產。
Macaca順便還提供了一個基於Ubuntu的Macaca-puppeteer的Docker。
4)、使用await page.waitFor('div.Card');
來等待頁面的指定元素加載完成
'use strict'; module.exports = app => { const { router, controller } = app; router.get('/', controller.home.index); router.post('/service/screenshot', controller.screenshot.screenshot); };
'use strict'; const Controller = require('egg').Controller; class ScreentshotController extends Controller { constructor(ctx) { super(ctx) this.dataValidate = { appkey: { type: 'string', required: true, allowEmpty: false }, url: { type: 'string', required: true, allowEmpty: false }, isPart: { type: 'boolean', required: true, allowEmpty: false } } } async screenshot() { const { ctx, service } = this // 校驗參數 ctx.validate(this.dataValidate) // 組裝參數 const payload = ctx.request.body || {} // 調用 Service 進行業務處理 const res = await service.screenshot.screenshot(payload) // ctx.body = res; // 設置響應內容和響應狀態碼 ctx.helper.success({ctx, res}) } } module.exports = ScreentshotController;
'use strict' const Service = require('egg').Service const puppeteer = require('puppeteer') const fs = require('fs'); const path = require('path'); const images = require("images"); const mineType = require('mime-types'); const APPKEY = "jingwhale"; const partTypeDefalt = { githubcommits:".commits-listing" }; var part = ""; class ScreenshotService extends Service { async base64img(file){//生成base64 let filePath = path.resolve(file); let data = fs.readFileSync( path.resolve(filePath)); let imageData = images(filePath); var backData = { base64: data, width: imageData.width(), height: imageData.height() } backData.base64 = new Buffer(data).toString('base64'); return backData; } async screenshot(payload) { const { ctx, service } = this if(payload.appkey!=APPKEY){ ctx.throw(404, 'appkey不正確!'); } const browser = await puppeteer.launch(); const page = await browser.newPage(); var path = 'screenshot.png' var backData = {}; var id = payload.id await page.goto(payload.url); part = page; var partId = payload.partId; if(payload.isPart){ if(payload.partType===1){//自定義 console.log(payload.partType) }else{//默認 partId = partTypeDefalt[payload.partType]; } var partArr = await page.$$(partId); part = partArr[0]; } // //調用頁面內Dom對象的screenshot 方法進行截圖 try { // 截圖 await part.screenshot({path: path, type: 'png'}).catch(err => { console.log('截圖失敗'); console.log(err); }); }catch (e) { console.log('執行異常'); ctx.throw(404, '執行異常') } finally { await page.close(); await browser.close(); } var base64imgData = this.base64img(path) return base64imgData } } module.exports = ScreenshotService
技術給我更多的受益是解決問題的方式與思路。
不少重複單一的任務,均可以使用技術解決。
提升效率,留出更多的時間去設計。
Work Smart,Think more,Do Less,Get More.