# ReactNative控制台的实现 仿阿里云的手机客户端,要实现控制台连接。github没有搜到合适的,所以决定自己实现。 ## 思路 之前在PC端使用过term.js,功能简单,逻辑清晰,但由于作者已经不再维护,因此本次使用xterm插件。https://github.com/xtermjs/xterm.js 实现的思路,就是在ReactNative中使用WebView加载网页,网页使用各种信息建立websocket连接,并初始化xterm插件。 跟PC端相比,移动端有以下不同点: * 手机端的软键盘,通常不具备Esc,Ctrl和方向键,需要额外实现软按钮 * 手机端的软键盘,可以收缩或弹出,要求控制台可以自使用缩放 * 软按钮和软键盘结合,实现组合键 (例如 ctrl+c) ## 软按键的实现 * 调研xterm的官方文档,寻找可以手动发送数据的接口。 找到了一个疑似的接口: write 但是测试后发现,这个接口仅仅是把明文写入到前端页面,并没有发送给websoket。 * 简单阅读以下xterm的源码,发现xterm对键盘的响应,是来源于一个textarea元素。当xterm初始化后,会立刻创建一个textarea,并绑定各种按键Listener。 因此想到了使用自定义事件来手动触发,如下: ``` $('textarea.xterm-helper-textarea')[0].dispatchEvent(new KeyboardEvent("keydown", { keyCode: 67 })); ``` 这行代码模拟了键盘按键c的操作,在PC上工作正常,但在android系统下无效。查看了相关论坛,发现不同核心的浏览器,对按键事件的处理方式不同。搜到了一位大神封装的函数,应对各个浏览器: ``` function fireKeyEvent(el, evtType, keyCode, ctrlKeyArg){ var ctrlKey = !!ctrlKeyArg; var doc = el.ownerDocument, win = doc.defaultView || doc.parentWindow, evtObj; if(doc.createEvent){ if(win.KeyEvent) { evtObj = doc.createEvent('KeyEvents'); evtObj.initKeyEvent( evtType, true, true, win, ctrlKey, false, false, false, keyCode, 0 ); } else { evtObj = doc.createEvent('UIEvents'); Object.defineProperty(evtObj, 'keyCode', { get : function() { return this.keyCodeVal; } }); Object.defineProperty(evtObj, 'which', { get : function() { return this.keyCodeVal; } }); evtObj.initUIEvent( evtType, true, true, win, 1 ); evtObj.keyCodeVal = keyCode; evtObj.ctrlKey = ctrlKey; if (evtObj.keyCode !== keyCode) { console.log("keyCode " + evtObj.keyCode + " 和 (" + evtObj.which + ") 不匹配"); } } el.dispatchEvent(evtObj); } else if(doc.createEventObject){ evtObj = doc.createEventObject(); evtObj.keyCode = keyCode; evtObj.ctrlKey = ctrlKey; el.fireEvent('on' + evtType, evtObj); } } ``` 例如你需要触发Esc按键时,执行代码: ``` fireKeyEvent($('textarea.xterm-helper-textarea')[0], "keydown", 27, false); ``` 其中27是Esc的keycode。经测试在安卓和IOS下均工作正常 ## 控制台自由缩放的实现 * 首先,在客户端使用KeyboardAvoidingView插件,防止键盘遮挡屏幕。 ``` <KeyboardAvoidingView behavior={'padding'} style={{flex: 1}}> <WebView> ... </WebView> </KeyboardAvoidingView> ``` 具体用法,可以参考RN官方文档 * 键盘展开或缩放,WebView的可视区域也响应变化。在网页内增加监听: ``` window.onresize = function(){ if(term){ term.resize(getUrlParam('width') / 7.5, parseInt((this.innerHeight - 40) / 14)) } } ``` 上述常量,根据具体的font-size以及网页布局来确定大小 ## 组合键的实现 1. 控制台最常用的组合键应该就是ctrl + c了。实现的思路是: > 用户按下虚拟按键 ctrl, 此时只是网页记录ctrl状态, 并不发送任何数据 > 在ctrl状态下,下一个软键盘的按键独立事件都会被禁止,使用一个组合按键事件来替换 2. xterm的自定义事件:attachCustomKeyEventHandler。 按照官方文档的描述,当有按键事件时,会执行回调,并根据回调的返回决定这个事件是否继续。 在PC端进行测试,可以监听到每个keydown事件。但是在安卓手机下,所有的keydown事件的键盘码都是229,可能是输入法导致。因此无法直接使用此接口。 3. 阅读xterm的源码,发现在有输入法的情况下,xterm并不依赖按键事件,而是根据textarea的变化量判断输入的内容。 很遗憾的是,xterm并没有对这方面暴露出相关接口。无奈,只能亲自修改源码。 ``` CompositionHelper.prototype._handleAnyTextareaChanges = function () { var _this = this; var oldValue = this._textarea.value; setTimeout(function () { if (!_this._isComposing) { var newValue = _this._textarea.value; var diff = newValue.replace(oldValue, ''); if (diff.length > 0) { if(_this._terminal.checkHandler(diff)){ _this._terminal.handler(diff); } } } }, 0); }; ``` _handleAnyTextareaChanges就是textarea发生变化后触发的事件,在其内部增加了自定义了函数checkHandler,并暴露出来。用法和attachCustomKeyEventHandler基本一致。 编写如下代码: ``` term.attachCheckHandler(function(key){ // 有输入法情况下,使用此函数捕捉键盘事件 if(ifControl){ // 接收到的数据是ascii字符,要根据对照表,转为对应的按键码 var output = Ascii2KeyCode(key.charCodeAt()); if(output && output != 229){ ifControl = false; // 发送组合键事件 fireKeyEvent($('textarea.xterm-helper-textarea')[0], "keydown", output, true); // 拒绝发送默认数据,阻止事件 return false; } } }) function Ascii2KeyCode(keycode){ var output; if(keycode >= 97 && keycode <= 122){ output = keycode - 32 }else if(keycode === 127){ output = 56 }else if(keycode === 91){ output = 219 }else if(keycode === 93){ output = 221 }else if(keycode === 229){ output = 229 } return output; } ```

共 1 条
  • 1
前往