AlloyRenderingEngine文字框元件

【當耐特】發表於2015-05-20

寫在前面

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;
            }
        }
    });
})();

大部分程式碼都做了解釋,不再重複闡述。

Github: https://github.com/AlloyTeam/AlloyGameEngine

相關文章