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的庫比較大,后續會改進為當用戶點擊反饋鏈接時進行異步加載。
當然,大家在體驗的過程中如果有什么意見和建議非常歡迎提出,一起完善。

浙公網安備 33010602011771號