實現Google帶截圖功能的web反饋插件

幾乎全部的APP應用包括Web應用都須要一個意見反饋,這樣才能瞭解用戶對產品的意見和建議,以便於不斷提高完善本身的產品。目前的反饋組件通常有兩種,一種是打開一個反饋頁面填寫表單,另外一種則是經過彈窗來完成,相比較而言第二種更加方便,並且更加容易組件化。
國內比較典型的有像知乎,百度這樣類型的反饋組件 css

國外則有谷歌的
因爲本人比較喜歡谷歌的material design,並且谷歌的反饋組件功能也比較齊全,仿照谷歌的組件寫一個本身的通用組件。 下面是PC和手機上的效果圖:
pc
mobile
demo演示
項目github地址

1.開發

首先根據谷歌的反饋插件分析須要實現哪些功能,這個組件在不少谷歌頁面中都會出現,谷歌搜索頁出現的位置是底部。 根據實際操做能知道這個組件至少要包含這些功能:html

  1. 截取當前屏幕;
  2. 能編輯頁面的高亮或者遮擋區域;
  3. 能夠適應pc於手機;

知道功能後就能一步步開始實現它了。谷歌是經過iframe來實現這個組件的,比較複雜 谷歌的feedback工具加載文件:load.js和截圖文件screenshot.min.js有興趣的同窗能夠看一下。
這裏使用本身的思路,使用現成模塊來簡單實現這個功能。node

1.截取屏幕

須要獲取當前屏幕內容,第一時間反應是使用canvas了,先把dom元素畫到canvas如何再生成圖片,幸虧有一個牛掰的模塊叫作html2canvas,它能夠將指定的DOM元素繪製到canvas上。react

安裝html2canvasgit

npm i html2canvas
複製代碼

html2canvas 1.0.0版本中可使用做者提供的html2canvas-proxy模塊來實現跨越資源代理。具體配置能夠參考文檔。順便提一下,以前0.5.0版本中,html2canvas提供的代理方便不太好使,解決的方法是本身啓動一個處理服務,在頁面中遇到跨域圖片資源,使用服務將圖片轉成base64格式而後回填到圖片src屬性上,這樣來實現跨越圖片截圖。github

若是頁面上不單單有圖片還有視頻該怎麼辦呢,怎麼截取視頻圖片呢?html2canvas是不支持截取video標籤內容的,可是html2canvas截圖時能夠渲染元素的背景圖片。那麼若是能夠獲取視頻當前播放的幀,把這一幀做爲video標籤的background,html2canvas就能讀取到了。
如何讀取video的幀,這個canvas的drawImage()方法就能作到(注意不能獲取跨域的視頻資源)。web

let video = videoItem[0];
if(!video.style.backgroundImage) {
    let w = $(video).width();
    let h = $(video).height();
    $(video).after('<canvas width="'+ w +'" height="'+ h +'"></canvas>');
    let canvas = $(video).next('canvas').css({display: 'none'});
    let ctx = canvas.get(0).getContext('2d');
    ctx.drawImage(video, 0, 0, w, h);
    try {
        video.style.backgroundImage = "url("+ canvas.get(0).toDataURL('image/png') +")";
    }catch (e) {
        console.log(e)
    }finally {
        canvas.remove();
    }
}
複製代碼

作完這些準備就能夠開始時截圖了express

html2canvas(document.body, {
    proxy: this.props.proxy || '',
    width: window.innerWidth,
    height: window.innerHeight,
    x: document.documentElement.scrollLeft || document.body.scrollLeft,
    y: document.documentElement.scrollTop || document.body.scrollTop,
}).then((canvas) => {
    let src = canvas.toDataURL('image/png');
    ...
}).catch((e) => {
    
});
複製代碼

注意:html2canvas v1.0.0使用promise,v0.5.0採用的是回調函數。npm

2.高亮區域

核心部分截圖搞定了,接下來就是高亮區域了。高亮分爲兩部分:canvas

  1. 鼠標放在頁面上識別當前鼠標是在哪一個DOM元素上而後將這個DOM元素高亮,給用戶提供一個快捷選區方式。
  2. 用戶本身用鼠標選區一個高亮區域。

第二點很容易實現,只有鼠標按下時記錄點擊點位置,而後隨着鼠標移動計算與初始點位置的差值就能獲得一個區域了,那麼怎麼識別鼠標是放在哪一個DOM元素上呢?有一個Web API能夠輕鬆實現這個功能那就是elementsFromPoint

elementsFromPoint() 方法能夠獲取到當前視口內指定座標處,由裏到外排列的全部元素。
複製代碼

使用方法很簡單,只要給x,y座標就好了。

var elements = document.elementsFromPoint(x, y);
複製代碼

這個方法返回的是一個包含當前鼠標所在位置的DOM元素數組。元素在數組中的位置與元素的z軸位置和元素包含關係有關,z軸越大位置越靠前,子元素比父元素靠前。

<div>
    <p>
        <img/>
    </p>
</div>
複製代碼

若是是這個結構那麼當鼠標在img標籤上document.elementsFromPoint(x, y)返回的值是這樣的

[img, p, div]
複製代碼

獲得元素後那麼後續的操做就簡單了,在一個半透明的黑色區域上摳出透明部分,顯然css是實現不了的,那麼就上canvas吧。
首先要畫一個半透明的遮罩:

let canvas = this.refs.canvas;
if (!this.ctx) {
    this.ctx = canvas.getContext('2d');
}
let docWidth = document.body.clientWidth,
    docHeight = document.body.clientHeight;
if(docHeight < window.innerHeight) {
    docHeight = window.innerHeight;
}
canvas.width = docWidth;
canvas.height = docHeight;
canvas.style.width = docWidth;
canvas.style.height = docHeight;
this.ctx.fillStyle = 'rgba(0,0,0,0.3)';
this.ctx.fillRect(0, 0, docWidth, docHeight);
複製代碼

準備完遮罩就能夠開始扣圖了。經過elementsFromPoint的到元素後使用getBoundingClientRect()方法獲得元素的位置信息。 getBoundingClientRect()用於得到頁面中某個元素的左,上,右和下分別相對瀏覽器視窗的位置以及元素寬高。

寬高,位置信息都有了就能夠開始繪製了:

this.ctx.lineWidth = '5';
this.ctx.strokeStyle = '#FEEA4E';
this.ctx.rect(x, y, width, height);
this.ctx.stroke();
this.ctx.clearRect(x, y, width, height);
複製代碼

同理若是不是高亮而是遮擋只要把清除區域換成繪製一個半透明黑色區域就能夠了。 要注意每次畫新區域時要清除上次繪製的內容因此每次都得初始化一次canvas內容

3.適配手機

因爲手機頁面中的反饋界面與PC差距太大因此不能採用同一套模板,經過一個state來區分該渲染那種類型。在組件willMount的時候判斷設備類型

let device = 'pc';
let ua = navigator.userAgent;
let ipad = ua.match(/(iPad).*OS\s([\d_]+)/),
    isIphone = !ipad && ua.match(/(iPhone\sOS)\s([\d_]+)/),
    isAndroid = ua.match(/(Android)\s+([\d.]+)/),
    isMobile = isIphone || isAndroid;
if (isMobile) {
    device = 'mobile';
    this.setState({
        device: device,
    });
}
複製代碼

獲得設備後根據設備進行渲染

{
    this.state.device == 'pc'?
    <PCComponent/>
    :
    <MobileComponnet>
}
複製代碼

須要注意的是有些手機瀏覽器在打開輸入法後會致使頁面窗口變化因此須要監聽窗口變化去作適配調整

2.使用

1.安裝:

使用npm

npm install react-googlefeedback --save-dev
複製代碼

2.使用

react中:

import React from 'react';
import ReactDOM from 'react-dom';
import Feedback from 'react-googlefeedback';
import 'react-googlefeedback/dist/style.css';

const license = `如出於法律緣由須要請求更改內容,請前往
                <a href="" >法律幫助</a>
                頁面。系統可能已將部分
                <a href="">賬號和系統信息</a>
                發送給Google。咱們將根據本身的
                <a href="">隱私權政策</a>和<a href="">服務條款</a>
                使用您提供的信息幫助解決技術問題和改進咱們的服務。`;

class Page extends React.Component {
    constructor() {
        super();
        this.state = {
            open: false,
        }
    }
    open() {
        this.setState({
            open: true,
        })
    }
    cancel() {
        this.setState({
            open: false,
        })
    }
    send(data) {
        console.log(data)
    }
    render() {
        return (
            <div>
                <button onClick={this.open.bind(this)}>feedback</button>
                {
                    this.state.open?
                        <Feedback
                            theme="#3986FF"
                            cancel={this.cancel.bind(this)}
                            send={this.send.bind(this)}
                            license={license}
                            proxy="http://127.0.0.1:5000"
                            title="發送反饋"
                            placeholder="請說明您的問題或分享您的想法"
                            requiredTip="必須添加說明"
                            editTip="點擊編輯高亮或隱藏信息"
                            loadingTip="正在加載屏幕截圖..."
                            checkboxLabel="包含截圖"
                            cancelLabel="取消"
                            confirmLabel="發送"
                        />:null
                }
            </div>
        )
    }
}
ReactDOM.render(<Page/>, document.getElementById('main'));
複製代碼

在頁面中引入js文件使用:

<body>
    <div id="feedback"></div>
    <button id="btn"></button>
<body>
<script src="react-googlefeedback/dist/googlefeedback.js"></script>
<script>
    new Feedback({
        container: document.getElementById('feedback'),
        trigger: document.getElementById('btn'),
        theme: '#3986FF',
        proxy: 'http://127.0.0.1:5000',
        title: '發送反饋',
        placeholder: '請說明您的問題或分享您的想法',
        requiredTip: '必須添加說明',
        editTip: '點擊編輯高亮或隱藏信息',
        loadingTip: '正在加載屏幕截圖...',
        checkboxLabel: '包含截圖',
        cancelLabel: '取消',
        confirmLabel: '發送',
        license: `如出於法律緣由須要請求更改內容,請前往
                <a href="" >法律幫助</a>
                頁面。系統可能已將部分
                <a href="">賬號和系統信息</a>
                發送給Google。咱們將根據本身的
                <a href="">隱私權政策</a>和<a href="">服務條款</a>
                使用您提供的信息幫助解決技術問題和改進咱們的服務。`,
        send: function (data) {
            console.log(data)
        }
    });
</script>
複製代碼

3.參數說明

react 組件:

參數 功能 類型 是否必填
theme 設置組件主題顏色 string ✗ 默認值 #3986FF
cancel 取消按鈕處理函數 function
send 發送按鈕處理函數,會傳回收集的數據 function
license 協議內容 html字符串 ✗ 默認值爲谷歌的隱私條款協議
proxy 代理地址,若是頁面中存在跨域資源能夠設置這個值 string ✗ 默認空值
title 彈出標題文字 string
placeholder 文字輸入提示 string
requiredTip 文字必填提示 string
editTip 提示編輯文子 string
loadingTip 加載圖片提示 string
checkboxLabel 勾選框文字 string
cancelLabel 取消按鈕文字 string
confirmLabel 確認按鈕文字 string

頁面中直接引用option參數:

參數 功能 類型 是否必填
container 組件容器元素 element
trigger 用於觸發組件打開的元素 element
theme 設置組件主題顏色 string ✗ 默認值 #3986FF
license 協議內容 html字符串 ✗ 默認值爲谷歌的隱私條款協議
proxy 代理地址,若是頁面中存在跨域資源能夠設置這個值 string ✗ 默認空值
title 彈出標題文字 string
placeholder 文字輸入提示 string
requiredTip 文字必填提示 string
editTip 提示編輯文子 string
loadingTip 加載圖片提示 string
checkboxLabel 勾選框文字 string
cancelLabel 取消按鈕文字 string
confirmLabel 確認按鈕文字 string
send 發送按鈕處理函數,會傳回收集的數據 function

4.跨域代理

須要啓動一個服務用於代理。
首先安裝html2canvas-proxy

npm install html2canvas-proxy --save
複製代碼

在node中使用代理

var proxy = require('html2canvas-proxy');
var express = require('express');
var app = express();
app.use('/', proxy());
複製代碼
相關文章
相關標籤/搜索