zoukankan      html  css  js  c++  java
  • artwlfeedback.js——仿google搜索结果页的“发送反馈”功能

    缘起

      不知道大家有没有用过google搜索结果页的“发送反馈”功能(还没有用过的,快去体验一下吧),个人用过后觉得非常酷,特别适合反馈界面视觉问题,于是就有了本文介绍的小作品。

      给不能FQ的截张图吧:

    效果

      不知道大家有没有注意到本页最下面有个“发送反馈”的固定链接,可以点击看看效果。下面是chrome下的效果:

      注:需要浏览器支持HTML5

    原理

      通过查看google搜索结果页反馈时的代码可以看到,是把页面生成了一个canvas,然后在canvas上画矩形来实现的:

      所以在不支持canvas的浏览器下,是没有这个效果的。

      我的方案是利用html2canvas库把页面内容渲染成一个canvas,然后利用canvas的画图功能来做标记,然后把canvas转换为base64格式的图片用于发送反馈。

    代码

      代码比较简单,就是先用调用html2canvas库把页面转换成一个canvas,然后把这个canvas添加到页面上,然后再创建一个空白的canvas用于画标记(矩形),添加一个空白的canvas的作用是如果标记有误方便清除。其他的就是canvas的画图的代码,最后当用户点击保存时合并前面创建的两个canvas,并利用canvas的toDataUrl方法把canvas转换为base64格式的png图片输出,后续的操作开发者就可以自己定义了。

      JS代码如下(canvas画图的代码有参考 Javascript实现canvas画图功能 一文):

    var artwlfeedback = (function(){
        var load =function(callback){
            html2canvas(document.body, {
                onrendered: function(canvas) {
                    canvas.id = "artwlfeedback_pagecanvas";
                    var cv = document.createElement("canvas");
                    cv.width = canvas.width;
                    cv.height = canvas.height;
                    cv.style.background = "#666";
                    cv.id = "artwlfeedback_canvas";
                    document.body.appendChild(cv);
                    document.body.appendChild(canvas);
                    init(callback);
                }
            });
        }
    
        var init = function(callback){
            var paint={
                init:function(){
                    this.addDrawTool();
                    this.load();
                    this.bind();
                },
                addDrawTool: function(){
                    var NewLine = '\n';
                    var drawToolHtml = '';
                    drawToolHtml+='    <div id="artwlfeedback_operate">'+NewLine;
                    drawToolHtml+='        <input id="artwlfeedback_clear" type="button" value=" " title="clear"/>'+NewLine;
                    drawToolHtml+='        <input id="artwlfeedback_cancel" type="button" value=" " title="cancel"/>'+NewLine;
                    drawToolHtml+='        <input id="artwlfeedback_save" type="button" value=" " title="save as image" />'+NewLine;
                    drawToolHtml+='    </div>'+NewLine;
                    var drawToolNode = document.createElement("div");
                    drawToolNode.id = "artwlfeedback_draw_tool";
                    drawToolNode.className = "artwlfeedback";
                    drawToolNode.innerHTML = drawToolHtml;
                    document.body.appendChild(drawToolNode);
                },
                load:function(){
                    this.x=[];//记录鼠标移动是的X坐标
                    this.y=[];//记录鼠标移动是的Y坐标
                    this.clickDrag=[];
                    this.Rectangles = [];
                    this.lock=false;//鼠标移动前,判断鼠标是否按下
                    this.storageColor="#000000";
                    this.$=function(id){return typeof id=="string"?document.getElementById(id):id;};
                    this.canvas=this.$("artwlfeedback_canvas");
                    this.pageCanvas = this.$("artwlfeedback_pagecanvas");
                    this.cxt=this.canvas.getContext('2d');
                    this.cxt.lineJoin = "round";//context.lineJoin - 指定两条线段的连接方式
                    this.cxt.lineWidth = 2;//线条的宽度
                    this.iptClear=this.$("artwlfeedback_clear");
                    this.cancel= this.$("artwlfeedback_cancel");
                    this.saveAs = this.$("artwlfeedback_save");
                    this.w=this.pageCanvas.width;//取画布的宽
                    this.h=this.pageCanvas.height;//取画布的高
                    this.touch =("createTouch" in document);//判定是否为手持设备
                    this.StartEvent = this.touch ? "touchstart" : "mousedown";//支持触摸式使用相应的事件替代
                    this.MoveEvent = this.touch ? "touchmove" : "mousemove";
                    this.EndEvent = this.touch ? "touchend" : "mouseup";
                    this.drawTool = this.$("artwlfeedback_draw_tool");
                    this.callback = callback;
                },
                bind:function(){
                    var t=this;
                    /*清除画布*/
                    this.iptClear.onclick=function(){
                        t.clear();
                        t.Rectangles.length = [];
                    };
                    this.cancel.onclick = function(){
                        t.removeNode(t.pageCanvas);
                        t.removeNode(t.canvas);
                        t.removeNode(t.drawTool);
                    };
                    /*保存*/
                    this.saveAs.onclick = function(){
                        //创建新canvas用于合并pageCanvas和canvas
                        var saveCanvas = document.createElement('canvas');
                        saveCanvas.width = t.w;
                        saveCanvas.height = t.h;
    
                        var saveCxt = saveCanvas.getContext('2d');
                        saveCxt.fillStyle = "#666";
                        saveCxt.fillRect(0, 0, t.w, t.h);
                        saveCxt.globalAlpha=1;
                        saveCxt.drawImage(t.canvas, 0, 0);
                        saveCxt.globalAlpha=0.5;
                        saveCxt.drawImage(t.pageCanvas, 0, 0);
    
                        t.removeNode(t.pageCanvas);
                        t.removeNode(t.canvas);
                        t.removeNode(t.drawTool);
    
                        //输出图片
                        var imgData = saveCanvas.toDataURL("image/png");
                        if(t.callback){
                            callback(imgData);
                        } else {
                            var w=window.open('about:blank','image from canvas');
                            w.document.write("<img src='"+imgData+"' alt='from canvas'/>");
                        }
                    };
                    /*鼠标按下事件,记录鼠标位置,并绘制,解锁lock,打开mousemove事件*/
                    this.canvas['on'+t.StartEvent]=function(e){
                        var touch=t.touch ? e.touches[0] : e;
                        var scrollTop = window.pageYOffset|| document.documentElement.scrollTop || document.body.scrollTop;
                        t.movePoint(touch.clientX - touch.target.offsetLeft,touch.clientY - touch.target.offsetTop + scrollTop);//记录鼠标位置
                        t.lock=true;
                        t.drawTool.style.display = "none";
                    };
                    /*鼠标移动事件*/
                    this.canvas['on'+t.MoveEvent]=function(e){
                        var touch=t.touch ? e.touches[0] : e;
                        if(t.lock)//t.lock为true则执行
                        {
                            var _x=touch.clientX - touch.target.offsetLeft;//鼠标在画布上的x坐标,以画布左上角为起点
                            var scrollTop = window.pageYOffset|| document.documentElement.scrollTop || document.body.scrollTop;
                            var _y=touch.clientY - touch.target.offsetTop + scrollTop;//鼠标在画布上的y坐标,以画布左上角为起点
                            t.movePoint(_x,_y,true);//记录鼠标位置
                            t.drawRectangle();
                        }
                    };
                    this.canvas['on'+t.EndEvent]=function(e)
                    {
                        /*重置数据*/
                        t.lock=false;
                        t.Rectangles.push([t.x[0], t.y[0], t.x[t.x.length -1] - t.x[0], t.y[t.y.length -1] - t.y[0]]);
                        t.x=[];
                        t.y=[];
                        t.clickDrag=[];
                        t.drawTool.style.display = "block";
                    };
                },
                movePoint:function(x,y,dragging){
                    /*将鼠标坐标添加到各自对应的数组里*/
                    this.x.push(x);
                    this.y.push(y);
                    this.clickDrag.push(y);
                },
                drawRectangle: function(){
                    var width = this.x[this.x.length-1] - this.x[0],
                        height = this.y[this.y.length-1] - this.y[0];
                    this.clear();
                    var i = this.Rectangles.length;
                    if(i){
                        for (i=i-1; i >= 0; i--) {
                            var rectangle = this.Rectangles[i],
                                r_x = rectangle[0],
                                r_y = rectangle[1],
                                r_width = rectangle[2],
                                r_height = rectangle[3];
                            this.cxt.strokeRect(r_x, r_y, r_width, r_height); // 只勾画出矩形的外框
                            this.cxt.fillStyle = "#FFFFFF";
                            this.cxt.fillRect(r_x, r_y, r_width, r_height); // 画出矩形并使用颜色填充矩形区域
                        };
                    }
                    this.cxt.strokeRect(this.x[0], this.y[0], width, height); // 只勾画出矩形的外框
                    this.cxt.fillStyle = "#FFFFFF";
                    this.cxt.fillRect(this.x[0], this.y[0], width, height); // 画出矩形并使用颜色填充矩形区域
                },
                clear:function(){
                    this.cxt.clearRect(0, 0, this.w, this.h);//清除画布,左上角为起点
                },
                removeNode: function(node){
                    node.parentNode.removeChild(node);
                }
            };
            paint.init();
        }
    
        return {
            load: load
        }
    })();

    调用

      关于如何调用可参考这里:http://afeedback.duapp.com/

    局限

      由于html2canvas有跨域限制,所以如果页面用了不同域下的图片(如本文)就不能正常显示。

      另外,由于html2canvas是根据HTML代码重新渲染成canvas,而有些css无法识别,会造成页面跟canvas上的不完全一致。

    改进空间和后续计划

      目前在不支持canvas的浏览器下没有任何效果,这个可改进为传统方式。

      html2canvas的库比较大,后续会改进为当用户点击反馈链接时进行异步加载。

      当然,大家在体验的过程中如果有什么意见和建议非常欢迎提出,一起完善。

    版权

    作者:Artwl

    出处:http://artwl.cnblogs.com

    本文首发博客园,版权归作者跟博客园共有。转载必须保留本段声明,并在页面显著位置给出本文链接,否则保留追究法律责任的权利。

  • 相关阅读:
    mysql用 法like concat()
    redis系列之数据库与缓存数据一致性解决方案
    day33:进程锁&事件&进程队列&进程间共享数据
    day32:进程&进程join&守护进程deamon
    day31:socketserver&hashlib&hmac&TCP登录
    day30:TCP&UDP:socket
    day29:计算机网络概念
    小程序3:ATM小程序
    hdu 6867 Tree 2020 Multi-University Training Contest 9 dfs+思维
    Codeforces Round #660 (Div. 2) Captain Flint and Treasure 拓扑排序(按照出度、入读两边拓扑排序)
  • 原文地址:https://www.cnblogs.com/artwl/p/3069563.html
Copyright © 2011-2022 走看看