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的库比较大,后续会改进为当用户点击反馈链接时进行异步加载。

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

posted @ 2013-05-09 19:22  artwl  阅读(2476)  评论(4编辑  收藏

个人简介

var ME = {
	"name": "土豆/Artwl",
	"job": "coding",
	"languages": [
		"JS", "HTML",
                "CSS", "jQuery"
		"MVC",".NET",
		"设计模式"
	],
	"hobby": [
		"阅读", "旅游",
		"音乐", "电影"
	]
}
TOP