Laya 文本相關(guān)類(TextArea高度自適應(yīng))

一、位于laya.display包下的兩個(gè)類

1.class Text extends Sprite
內(nèi)部使用了graphics.fillBorderText繪制文本

2.class Input extends Text
內(nèi)部封裝了原生的文本輸入框,并且是全局唯一的

private static function _createInputElement():void {
    _initInput(area = Browser.createElement("textarea"));
    _initInput(input = Browser.createElement("input"));
    
    inputContainer = Browser.createElement("div");
    inputContainer.style.position = "absolute";
    inputContainer.style.zIndex = 1E5;
    Browser.container.appendChild(inputContainer);
    //[IF-SCRIPT] inputContainer.setPos = function(x:int, y:int):void
    { inputContainer.style.left = x + 'px'; inputContainer.style.top = y + 'px'; };
}

在focusin時(shí)把原生的textarea或input添加到div中顯示出來(lái),在focusout時(shí)再移除。

//Input.as
public function set focus(value:Boolean):void {
    var input:* = nativeInput;
    
    if (_focus !== value) {
        if (value) {
            if (input.target) {
                input.target._focusOut();
            } else {
                _setInputMethod();
            }
            input.target = this;
            
            _focusIn();
        } else {
            input.target = null;
            _focusOut();
            Browser.document.body.scrollTop = 0;
            input.blur();
            
            if (Render.isConchApp) {
                input.setPos(-10000, -10000);
            } else if (inputContainer.contains(input))
                inputContainer.removeChild(input);
        }
    }
}

private function _setInputMethod():void {
    input.parentElement && (inputContainer.removeChild(input));
    area.parentElement && (inputContainer.removeChild(area));
    
    inputElement = (_multiline ? area : input);
    inputContainer.appendChild(inputElement);
    if (Text.RightToLeft)
    {
        inputElement.style.direction = "rtl";
    }
}

3.自動(dòng)獲取焦點(diǎn),并彈出鍵盤
參考彈出一個(gè)面板 想讓焦點(diǎn)自動(dòng)在輸入文本上且彈出手機(jī)鍵盤,官方答復(fù)是:“軟鍵盤彈出必須有觸發(fā)行為,不支持直接默認(rèn)彈出虛擬鍵盤!”
其實(shí)在安卓上可以做到的,設(shè)置完input.focus=true之后,再調(diào)用_popupInputMethod即可。但I(xiàn)OS上無(wú)效。

public focusInInput() {
    this.chatInput.inputChat.focus = true;
    //移動(dòng)平臺(tái)在單擊事件觸發(fā)后彈出輸入法
    var layaInput:any = Laya.Input;
    layaInput._popupInputMethod();
}

// 移動(dòng)平臺(tái)在單擊事件觸發(fā)后彈出輸入法
private static function _popupInputMethod(e:*):void {
    //e.preventDefault();
    if (!Input.isInputting) return;
    
    var input:* = Input.inputElement;
    
    // 彈出輸入法。
    input.focus();
}
二、位于laya.ui包下的兩個(gè)類

1.class Component extends Sprite

2.class Label extends Component
//內(nèi)部封裝了一個(gè)Text,全是調(diào)用Text的方法,基本沒(méi)有新代碼。
protected var _tf:Text;

3.class TextInput extends Label

/**@inheritDoc */
override protected function createChildren():void {
    addChild(_tf = new Input());
    _tf.padding = Styles.inputLabelPadding;
    _tf.on(Event.INPUT, this, _onInput);
    _tf.on(Event.ENTER, this, _onEnter);
    _tf.on(Event.BLUR, this, _onBlur);
    _tf.on(Event.FOCUS, this, _onFocus);
}

在這段代碼中,把_tf實(shí)例化為一個(gè)Input(Input是Text的一個(gè)子類,沒(méi)有毛病)。所以可以看作TextInput是對(duì)Input的一個(gè)封裝,比如很多方法是這樣的:

public function get maxChars():int {
    return Input(_tf).maxChars;
}

public function set maxChars(value:int):void {
    Input(_tf).maxChars = value;
}

4.class TextArea extends TextInput
內(nèi)部封裝了兩個(gè)滾動(dòng)條,還有一些控制邏輯。

/**@private */
protected var _vScrollBar:VScrollBar;
/**@private */
protected var _hScrollBar:HScrollBar;
三、在處理高度自適應(yīng)時(shí),追蹤了height屬性

在class TextInput extends Label看到這段:

//TextInput.as
override protected function initialize():void {
    width = 128;
    height = 22;
}

override public function set height(value:Number):void {
    super.height = value;
    _bg && (_bg.height = value);
}

調(diào)用了super.height,所以去Label.as中看一看:

//Label.as
override public function set height(value:Number):void {
    super.height = value;
    _tf.height = value;
}

調(diào)用了_tf.height,所以去了Text.as中:

//Text.as
override public function set height(value:Number):void {
    if (value != _height) {
        super.height = value;
        isChanged = true;
    }
}

Text.as的super.height就是指向Sprite.as類了

//Sprite.as
public function set height(value:Number):void {
    if (this._height !== value) {
        this._height = value;
        conchModel && conchModel.size(this._width, value);
        repaint();
    }
}

再往回看,因?yàn)閏lass Input extends Text,所以Input類繼承了Text類的 set height,但是Input類的構(gòu)造方法中,又指定了寬高值:

Input.as
/**創(chuàng)建一個(gè)新的 <code>Input</code> 類實(shí)例。*/
public function Input() {
    _width = 100;
    _height = 20;
    
    multiline = false;
    overflow = Text.SCROLL;
    
    on(Event.MOUSE_DOWN, this, _onMouseDown);
    on(Event.UNDISPLAY, this, _onUnDisplay);
}
四、進(jìn)入正題,textarea高度自適應(yīng)

在上述引擎源碼分析中,可以看到class Input extends Text類中,是有一個(gè)原生的textarea的。作為靜態(tài)屬性,是全局唯一的,在Laya.init()中就會(huì)將其初始化。在HTML頁(yè)面中也能看到基本屬性:

<textarea style="position: absolute; overflow: hidden; resize: none;
 transform-origin: 0px 0px 0px; background-color: transparent; border: none;
 outline: none; z-index: 1; white-space: pre-wrap; color: rgb(51, 51, 51);
 font-size: 32px; font-family: Arial; line-height: 32px; font-style: normal;
 font-weight: normal; text-align: left; padding: 0px; width: 532px;
 height: 62px;" maxlength="100000" placeholder="">
</textarea>

所以要找到原生JS是如何處理textarea高度自適應(yīng),這里參考SegmentFault 如何創(chuàng)建一個(gè)高度自適應(yīng)的textarea
第一種方式是把textarea替換成一個(gè)div,再把div的contentEditable設(shè)置為true。

第二種就是利用scrollHeight,設(shè)置到height上。這個(gè)可以參考http://www.jacklmoore.com/autosize/,我最終使用的就是這個(gè)庫(kù)。當(dāng)然也有手寫的,這個(gè)網(wǎng)上帖子也比較多,可以參考textarea如何實(shí)現(xiàn)高度自適應(yīng)(不出現(xiàn)滾動(dòng)條)?

第三種是1樓高贊回復(fù),在Textarea同級(jí)放一個(gè)pre 和span標(biāo)簽,然后把輸入內(nèi)容實(shí)時(shí)同步到span里。pre會(huì)隨內(nèi)容的高度變化而變化,expandingArea的高度又隨pre變化,因?yàn)閠extarea的高度100% textarea的高度會(huì)隨expandingArea變化,只要同步textarea的內(nèi)容到pre中,就達(dá)到一個(gè)textarea隨內(nèi)容高度變化的目的了。

<div class="expandingArea">
    <pre><span></span><br></pre>
    <textarea placeholder="輸入文字"></textarea>
</div>
五、使用jackmoore autosize庫(kù)遇到的一些問(wèn)題

1.autosize庫(kù)源碼中的resize方法有這樣兩行:

ta.style.height = '';
ta.style.height = ta.scrollHeight + heightOffset + 'px';

將height置為空串這個(gè)操作,經(jīng)過(guò)我的測(cè)試,如果注釋掉的話,在刪除一行時(shí),textarea不會(huì)自動(dòng)縮回去。但是這行代碼是有副作用的,那就是在輸入第一行時(shí),會(huì)多出來(lái)一個(gè)空行。后來(lái)參考論壇回復(fù),把rows設(shè)置為1解決了。在Laya中的代碼可以在Laya.init執(zhí)行后去操作Textarea的屬性。

//Input.as
/**@private */
protected static var input:*;
/**@private */
protected static var area:*;
/**@private */
protected static var inputElement:*;
/**@private */
protected static var inputContainer:*;

private static function _createInputElement():void {
    _initInput(area = Browser.createElement("textarea"));
    _initInput(input = Browser.createElement("input"));
    ……

area無(wú)法直接訪問(wèn),所以只能這樣寫了:

Laya.init(750, 1334, Laya.WebGL);
Laya.Input["area"].rows = 1;

2._syncInputTransform在手機(jī)上不執(zhí)行
在Input.as的_focusIn方法中,最后有這么一段:

// 輸入框重定位。
_syncInputTransform();
if (!Render.isConchApp && Browser.onPC)
    Laya.timer.frameLoop(1, this, _syncInputTransform);

為什么只有在Browser.onPC時(shí),才執(zhí)行這個(gè)重定位呢,原因不明。那么只能偵聽(tīng)Laya.Event.INPUT事件,自己再手動(dòng)執(zhí)行_syncInputTransform方法了。

/**
 * 在輸入期間,如果 Input 實(shí)例的位置改變,調(diào)用_syncInputTransform同步輸入框的位置。
 */
private function _syncInputTransform():void {
    var inputElement:Object = nativeInput;
    var transform:Object = 
    Utils.getTransformRelativeToWindow(this, padding[3], padding[0]);
    var inputWid:int = _width - padding[1] - padding[3];
    var inputHei:int = _height - padding[0] - padding[2];
    if (Render.isConchApp) {
        inputElement.setScale(transform.scaleX, transform.scaleY);
        inputElement.setSize(inputWid, inputHei);
        inputElement.setPos(transform.x, transform.y);
    } else {
        //[IF-SCRIPT]inputContainer.style.transform = 
        inputContainer.style.webkitTransform = 
        "scale(" + transform.scaleX + "," + 
        transform.scaleY + ") rotate(" + (Laya.stage.canvasDegree) + "deg)";
        //[IF-SCRIPT]inputElement.style.width = inputWid + 'px';
        //[IF-SCRIPT]inputElement.style.height = inputHei + 'px';
        //[IF-SCRIPT]inputContainer.style.left = transform.x + 'px';
        //[IF-SCRIPT]inputContainer.style.top  = transform.y + 'px';
    }
}

這里原始的_syncInputTransform會(huì)考慮padding后,去設(shè)置style.width和height。因?yàn)槲覀円呀?jīng)在autosize中計(jì)算了寬高,所以_syncInputTransform方法中就不用再計(jì)算了,只要設(shè)置inputContainer.style.left和top即可。

六、彈出鍵盤不遮擋輸入框

參考H5移動(dòng)端彈出鍵盤時(shí)遮擋輸入框,使用了文中第一種方式

private initLayaInput():void{
    var ta:any = Laya.Input["area"];
    //避免textarea出現(xiàn)空行
    ta.rows = 1;
    ta.onfocus = this.taFocusIn;
    ta.onblur = this.taFocusOut;
}

private taFocusIn: any = this.delayScrollBody.bind(this);
private delayScrollBody(): void {
    Laya.timer.loop(500,this,this.scrollDocBody);
}

private scrollDocBody():void{
    Laya.Browser.document.body.scrollTop = 
    Laya.Browser.document.body.scrollHeight;
}

private taFocusOut: any = this.removeDelayScrollBody.bind(this);
private removeDelayScrollBody(): void {
    Laya.timer.clear(this,this.scrollDocBody);
}

另外,也可以參考移動(dòng)端iOS第三方輸入法遮擋底部input及android鍵盤回落后留白問(wèn)題

七、焦點(diǎn)控制
//Input.as
/**創(chuàng)建一個(gè)新的 <code>Input</code> 類實(shí)例。*/
public function Input() {
    _width = 100;
    _height = 20;
    
    multiline = false;
    overflow = Text.SCROLL;
    
    on(Event.MOUSE_DOWN, this, _onMouseDown);
    on(Event.UNDISPLAY, this, _onUnDisplay);
}

private function _onUnDisplay(e:Event = null):void {
    focus = false;
}

private function _onMouseDown(e:Event):void {
    focus = true;
}

// 移動(dòng)平臺(tái)最后單擊畫布才會(huì)調(diào)用focus
// 因此 調(diào)用focus接口是無(wú)法都在移動(dòng)平臺(tái)立刻彈出鍵盤的
public function set focus(value:Boolean):void {
    var input:* = nativeInput;
    
    if (_focus !== value) {
        if (value) {
            if (input.target) {
                input.target._focusOut();
            } else {
                _setInputMethod();
            }
            input.target = this;
            
            _focusIn();
        } else {
            input.target = null;
            _focusOut();
            Browser.document.body.scrollTop = 0;
            input.blur();
            
            if (Render.isConchApp) {
                input.setPos(-10000, -10000);
            } else if (inputContainer.contains(input))
                inputContainer.removeChild(input);
        }
    }
}

這里看到,偵聽(tīng)Event.MOUSE_DOWN獲得了焦點(diǎn)。那么點(diǎn)擊INPUT之外的區(qū)域,是怎么失去焦點(diǎn)的呢。經(jīng)過(guò)查找,在MouseManager.as中找到了:

private function onMouseDown(ele:*):void {
    if (Input.isInputting && Laya.stage.focus && 
    Laya.stage.focus["focus"] && !Laya.stage.focus.contains(_target)) {
        // 從UI Input組件中取得Input引用
        // _tf 是TextInput的屬性
        var pre_input:* = Laya.stage.focus['_tf'] || Laya.stage.focus;
        var new_input:Input = ele['_tf'] || ele;
        
        // 新的焦點(diǎn)是Input的情況下,不需要blur;
        // 不過(guò)如果是Input和TextArea之間的切換,還是需要重新彈出輸入法;
        if (new_input is Input && new_input.multiline == pre_input.multiline)
            pre_input['_focusOut']();
        else
            pre_input.focus = false;
    }
    TouchManager.I.onMouseDown(ele, _tTouchID, _isLeftMouse);
}

參考一下Input.focus代碼,可以看出執(zhí)行focus=false與執(zhí)行_focusOut方法的區(qū)別。最重要一點(diǎn)是,focus=false會(huì)額外執(zhí)行input.blur(),這將導(dǎo)致收起軟鍵盤

// 移動(dòng)平臺(tái)最后單擊畫布才會(huì)調(diào)用focus
// 因此 調(diào)用focus接口是無(wú)法都在移動(dòng)平臺(tái)立刻彈出鍵盤的
public function set focus(value:Boolean):void {
    var input:* = nativeInput;
    
    if (_focus !== value) {
        if (value) {
            if (input.target) {
                input.target._focusOut();
            } else {
                _setInputMethod();
            }
            input.target = this;
            
            _focusIn();
        } else {
            input.target = null;
            _focusOut();
            Browser.document.body.scrollTop = 0;
            input.blur();
            
            if (Render.isConchApp) {
                input.setPos(-10000, -10000);
            } else if (inputContainer.contains(input))
                inputContainer.removeChild(input);
        }
    }
}

默認(rèn)情況下,在點(diǎn)擊聊天發(fā)送按鈕后,輸入框input會(huì)失去焦點(diǎn),導(dǎo)致軟鍵盤收起。這時(shí)繼續(xù)輸入,就要重新點(diǎn)擊輸入框input,是不是很煩。但是想更改這個(gè)默認(rèn)設(shè)定,也只能改源碼了……

if ((new_input instanceof laya.display.Input )&& new_input.multiline==pre_input.multiline)
    pre_input['_focusOut']();
else if((new_input instanceof laya.ui.Button )&& new_input.name == "sendBtn"){
    //聊天點(diǎn)擊發(fā)送按鈕(name==sendBtn),輸入框不會(huì)失去焦點(diǎn)
}else{
    pre_input.focus=false;
}
八、沒(méi)有MOUSE_UP事件

在輸入框輸入文本時(shí),直接點(diǎn)擊發(fā)送按鈕,在部分瀏覽器上會(huì)出現(xiàn)只是縮回鍵盤,文本并未發(fā)出的情況。經(jīng)過(guò)檢查,是沒(méi)有拋出MOUSE_UP事件,進(jìn)而導(dǎo)致沒(méi)有CLICK事件。將發(fā)送按鈕改為偵聽(tīng)MOUSE_DOWN事件即可。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,197評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,415評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,104評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,884評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,647評(píng)論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,130評(píng)論 1 323
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,208評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,366評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,887評(píng)論 1 334
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,737評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,939評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,478評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,174評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,586評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,827評(píng)論 1 283
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,608評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,914評(píng)論 2 372

推薦閱讀更多精彩內(nèi)容

  • 問(wèn)答題47 /72 常見(jiàn)瀏覽器兼容性問(wèn)題與解決方案? 參考答案 (1)瀏覽器兼容問(wèn)題一:不同瀏覽器的標(biāo)簽?zāi)J(rèn)的外補(bǔ)...
    _Yfling閱讀 13,775評(píng)論 1 92
  • 一、《保質(zhì)期》 任何東西都有保質(zhì)期 比如剛剛做好的冰淇淋 比如你和我之間的感情 二、《青蛙王子》 像最后一次分別前...
    不敢說(shuō)愛(ài)你閱讀 257評(píng)論 0 2
  • 午加餐:餅干干晚水果:香蕉 參考目標(biāo): 1份豆2份肉3份“新鮮”水果4份谷物/薯5份蔬菜,深綠色葉菜最好6杯水 今...
    靜趣_兒童心理師閱讀 267評(píng)論 0 0
  • 約不可失 魏文侯與虞人期獵。是日,飲酒樂(lè),天雨。文侯將出,左右曰:“今日飲酒樂(lè),天又雨,公將焉之?”文侯曰:“吾與...
    學(xué)霸愛(ài)學(xué)習(xí)閱讀 317評(píng)論 0 0