寫在前面
Github: https://github.com/AlloyTeam/AlloyGameEngine
在dom元素裡,自帶了input標籤,設定其type為text,它就是一個文字框。
那麼在Canvas中模擬input文字框是不是閒的沒事找事?絕對不是!
因為在遊戲當中可以統一化畫素管理,具體統一化畫素管理有什麼好處,以後新開文章詳細討論。
演示
上面的文字框就是使用AlloyRenderingEngine渲染出來的。
使用
; (function () { var Stage = ARE.Stage, Textbox = ARE.Textbox; var stage = new Stage("#ourCanvas", true); var textbox = new ARE.Textbox({ fontSize: 22, color: "red", width: 200, height: 26 }); textbox.x = 50; textbox.y = 50; textbox.focus(); stage.add(textbox); })();
原理(都在註釋裡)
; (function () { //先把要使用類的賦給臨時變數,以後就不用打點了:) var Stage = ARE.Stage, Container = ARE.Container, Graphics = ARE.Graphics, Text = ARE.Text; //文字框整合自容器 ARE.Textbox = Container.extend({ //建構函式 ctor: function (option) { //把容器的屬性和方法搞給自己 this._super(); //滑鼠移上去指標的形狀,AlloyRenderingEngine會自動幫你顯示滑鼠移上去時候的形狀 this.cursor = "text"; //文字框的邊框 this.box = new Graphics() //直接根據傳進的寬和高畫個矩形 this.box.strokeRect(0, 0, option.width, option.height); //文字框的背景,這裡接近透明,為什麼要設定背景是因為滑鼠一上去要觸發一個事件, //而AlloyRenderingEngine的預設觸發是畫素級別, //會根據getImageData得到該點的rgba的a是否為0去判斷是否觸發事件 //所以鋪一個接近透明的背景 //主要是為了觸發的事件是:滑鼠移到文字框上面,滑鼠形狀要變成cursor:text this.box.fillStyle("rgba(255,255,255,0.1)").fillRect(0, 0, option.width, option.height); //把邊框新增到自身(因為自身就是容器,繼承自Container,所以有了add方法) this.add(this.box); //繫結事件 this._bindEvent(); //合併預設配置 this.option = { fontSize: option.fontSize || 12, fontFamily: option.fontFamily || "arial", color: option.color || "black", width: option.width }; //cursorText代表文字框中閃爍的游標,自己用黑色的Text去模擬 this.cursorText = new Text("|", this.option.fontSize + "px " + this.option.fontFamily, "black"); //真正的input!!!!哈哈,玄機就在於此 = =! this.realTextbox = document.createElement("input"); this.realTextbox.type = "text"; this.realTextbox.style.position = "fixed"; this.realTextbox.style.left= "-200px" this.realTextbox.style.top= "0px" document.body.appendChild(this.realTextbox); //canvas中顯示的文字 this.text = new Text("", this.option.fontSize + "px " + this.option.fontFamily, this.option.color); //measureCtx是專門用於測量canvas中文字寬度的 this.measureCtx = document.createElement("canvas").getContext("2d"); this.measureCtx.font = this.option.fontSize + "px " + this.option.fontFamily; this.add(this.text, this.cursorText); //tickFPS是該容器tick執行的頻率,AlloyRenderingEngine會自動幫你執行tick方法 this.tickFPS = 20; }, //獲取焦點 focus: function () { var self = this; //真正的input也同時獲取焦點 this.realTextbox.focus(); //Canvas中的游標閃爍 this.loop = setInterval(function () { self.cursorText.visible = !self.cursorText.visible; }, 500); }, //失去焦點 blur: function () { clearInterval(this.loop); //真正的input也同時失去焦點 this.realTextbox.blur(); //隱藏Canvas中的游標 this.cursorText.visible = false; }, _bindEvent: function () { var self = this; this.onClick(function (evt) { //真正的input也同時獲取焦點 self.realTextbox.focus(); //顯示游標 self.cursorText.visible = true; //自己也假裝獲取焦點 self.focus(); //阻止冒泡 evt.stopPropagation(); }); //點選文字框的其他區域觸發失去焦點 document.addEventListener("mousedown", function () { //失去焦點 self.blur(); }, false); }, //計算合適的顯示文字,這主要是解決文字超出了文字框的寬度時候的顯示問題 getFitStr: function (str, index) { //利用measureText計算文字寬度 var width = this.measureCtx.measureText(str.substring(index, str.length - 1)).width; if (width < this.option.width - this.option.fontSize) { return this.getFitStr(str, --index); } else { return str.substring(index++, str.length - 1) } }, tick: function () { //利用measureText計算文字寬度,並把該寬度賦值給游標的偏移 this.cursorText.x = this.measureCtx.measureText(this.realTextbox.value).width; //如果寬度超了 if (this.cursorText.x > this.option.width) { this.text.value = this.getFitStr(this.realTextbox.value, this.realTextbox.value.length - 2); this.cursorText.x = this.measureCtx.measureText(this.text.value).width; } else {//如果寬度沒超 this.text.value = this.realTextbox.value; } } }); })();
大部分程式碼都做了解釋,不再重複闡述。