最近开发app协同聊天模块时,碰到个特别想优化的东西,就是聊天输入框的自适应,想做成微信那样,文本过多时,输入框自动变高,同时要保证聊天内容不会被输入框覆盖。记录下探索历程和大神实现思路。
使用可编辑的DIV
- CSS样式,不设置具体高度,但是可以设置上限与下限
.edit-box{ width: 100%; min-height: 20px; max-height: 100px; margin-left: auto; margin-right: auto; padding: 3px; outline: 0; border-radius: 5px; font-size: 12px; word-wrap: break-word; overflow-x: hidden; overflow-y: auto; -webkit-user-modify: read-write-plaintext-only; -webkit-user-select: auto; }
实现双向绑定
angular.module('starter.directive') .directive('contenteditable', function () { return { require: 'ngModel', link: function (scope, element, attrs, ngModel) { // 指定UI的更新方式 ngModel.$render = function () { element.html(ngModel.$viewValue || ''); }; // 监听change事件来开启绑定 element.on('blur keyup change', function () { // scope.$apply(read); read(); }); //read(); // 初始化 // 将数据写入model function read() { var html = element.html(); // 当我们清空div时浏览器会留下一个<br>标签 // 如果制定了strip-br属性,那么<br>标签会被清空 if (attrs.stripBr && html == '<br>') { html = ''; } ngModel.$setViewValue(html); } } } })
- 不足之处
当我沾沾自喜的时候,问题来啦,原因就是当输入内容过多换行时,ion-footer-bar
的高度变高了,但是ion-content
的高度或者bottom属性值,还是按照最初的高度来,这样就会出现容器溢出的情况,消息内容多时,消息会被输入框挡住,从而体验很不友好,我们可以通过设置ion-content
的高度为height:calc(100vh-180px)
,通过给ion-footer-bar
预留足够空间的方式来达到消息不被挡住的目的,但是这样同样不友好。最好的方式就是当ion-footer-bar
高度发生变化时,重新设置ion-content
的height或者bottom值。知道window对象有resize事件,但是普通div元素对此事件并不生效,查阅资料,发现有angular-elastic
这么个东东。
元素位置与大小属性
- clientHeight & clientWidth:可视区、客户端大小,包括内容区和内边距,不包括边框。
- clientLeft & clientTop:少用
- offsetHeight&offsetWidth:得到对象的大小,由自身宽高+内边距+边框构成,但是不包括外边距
- offsetHeight和style.height的区别
style.height只能获取行内样式,offsetHeight可以获取行内样式和内嵌样式 style.height是字符串(而且带单位),offsetHeight是数值 style.height可读可写,offsetHeight是只读属性
- offsetHeight和style.height的区别
- offsetLeft & offsetTop:得到对象的位置,到距离自身最近的(带有定位的)父元素的左侧/顶部 的距离,如果所有父级都没有定位则以body 为准
- 区别和offset家族类似
- scrollHeight & scrollWidth:对象内部实际内容的高度/宽度,包括内容区和内边距,不包括边框
- scrollLeft & scrollTop:被卷去部分的顶部/左侧到可视区域顶部/左侧的距离
- innerHeight & innerWidth:浏览器可见区域的内宽度、高度(不含浏览器的边框,但包含滚动条),一般用于window对象。
- outerWidth & outerHeight:浏览器外宽度(包含浏览器的边框,因各个浏览器的边框边一样,得到的值也是不一样的)。
TEXTAREA实现
主要通过TEXTAREA
结合angular-elastic
实现,angular-elastic所做的事情就是当内容过多时自动增高,通过发布resize事件,这样一来对于ion-content
和ion-footer-bar
的协调就会变得很灵活。
- 一直不知道textArea标签右下角的点点点是干嘛的,很丑诶,原来他是用来控制伸缩的,默认水平垂直都可以伸缩,也可以设置仅水平或垂直,关闭直接设置
resize:none
,同时也不会展示出来。 angular-elastic
使用- textArea添加msd-elastic属性或class
- 监听
elastic:resize
事件,实现自己的逻辑 - 设置rows为1,默认为2
源码思路分析
- 基本api
- window.getComputedStyle:得到元素最终样式
- style.getPropertyValue得到样式值,k-v的方式
- 创建一个镜像mirror
- 确保影响文本长度的样式一致(字体,大小等)
- 监听文本变化,将内容赋值给镜像,然后得到镜像的高度,很真身比较,发生变化则调整为镜像高度,同时发布变化事件
- 监听作用域销毁事件,从dom中移除镜像,避免dom污染
键盘弹出
这么一来,弹出表情选择框,工具框,输入多文本,表现的都很完美,但是还有一个小问题,就是键盘弹出来时会存在消息挡住的情况,原因就是ion-content
将键盘的高度算进去了,那么如何得到键盘的高度呢,有没有一脸懵逼,后面转念一想,键盘弹出来时,window.innerHeight
的高度有没有发生变化呢,bingo,键盘弹出时,确实发生了变化,那么问题也就解决了,直接在键盘弹出和消失时触发resize时间,重新计算即可,这里也贴出resizeFootBar指令代码。
- resizeFootBar
.directive('resizeFootBar', ['$ionicScrollDelegate', "$window", function ($ionicScrollDelegate, $window) { return { replace: false, link: function (scope, iElm, iAttrs, controller) { //绑定taResize事件 scope.$on("elastic:resize", function (e, ta) { var foot_height = iElm[0].offsetHeight + 10; var scroll = document.body.querySelector("#message-detail-content"); var scrollBar = $ionicScrollDelegate.$getByHandle('messageDetailsScroll'); scroll.style.height = $window.innerHeight - 44 - foot_height + "px" //滑动到底部 scrollBar.scrollBottom(true); }); } } }])
- 键盘事件
Bingo!完美//文本框聚焦 出现输入法 window.addEventListener("native.keyboardshow", function (e) { $timeout(function () { $scope.$broadcast('elastic:resize'); }, 50); }); window.addEventListener("native.keyboardhide", function (e) { $timeout(function () { $scope.$broadcast('elastic:resize'); }, 50); });
IOS输入框挡住
输入框位于底部时,键盘弹出时会看不见输入框,因为输入框被键盘挡住了,之前的解决办法比较简单干脆,但是如今发现其实很不友好,代码如下:
cordova.plugins.Keyboard.disableScroll(false);
但是这样设置存在问题,整个内容区域都会往上提,以至于标题都看不见了。
- 为什么ios和android表现不一样?
经过简单的测试,在android设备上,键盘出现时,整个ion-view
和ion-content
的高度都会发生变化,而在ios设备上,键盘弹出和消失,ion-view
和ion-content
的高度的并没有发生变化,键盘就好像浮在上方一样。问题原因出来了,解决办法也就来了,手动设置ion-content
的高度和ion-footer-bar
的bottom值(android会发生变化,无须设置bottom,ios必须设置),那么如何得到键盘的高度呢,通过cordova.plugins.Keyboar插件我们是可以得到高度的。 - 有两个点需要注意:
- IOS
ion-view
和ion-content
的高度不发生变化,但是window.innerHeight发生变化 - IOS的状态栏占了20px,需要多减去20px
- IOS
- 代码优化:
键盘事件,e.keyboardHeight得到键盘高度.directive('resizeFootBar', ['$ionicScrollDelegate', "$window", function ($ionicScrollDelegate, $window) { return { replace: false, link: function (scope, iElm, iAttrs, controller) { //绑定taResize事件 scope.$on("elastic:resize", function (e, ta) { var foot_height = iElm[0].offsetHeight + 10; var scroll = document.body.querySelector("#message-detail-content"); var scrollBar = $ionicScrollDelegate.$getByHandle('messageDetailsScroll'); if(ionic.Platform.isIOS()){ scroll.style.height = $window.innerHeight - 64 - foot_height + "px"; if(ta&&ta.type=='keyboardshow'&&ta.keyboardHeight){ iElm[0].style.bottom=ta.keyboardHeight + 'px'; } if(ta&&ta.type=='keyboardhide'){ iElm[0].style.bottom='0px'; } }else{ scroll.style.height = $window.innerHeight - 44 - foot_height + "px" } //滑动到底部 scrollBar.scrollBottom(true); }); } } }])
//文本框聚焦 出现输入法 window.addEventListener("native.keyboardshow", function (e) { $timeout(function () { $scope.$broadcast('elastic:resize',{type:'keyboardshow',keyboardHeight:e.keyboardHeight}); }, 50); }); window.addEventListener("native.keyboardhide", function (e) { $timeout(function () { $scope.$broadcast('elastic:resize',{type:'keyboardhide',keyboardHeight:e.keyboardHeight}); }, 50); });
键盘闪烁
碰到一个很恶心的问题,如果需要连续发送消息,点击发送按钮时,输入框焦点转移,因此键盘会消失,如果在发送的click事件处理中让输入框重新获取焦点,键盘会先消失再出现,这一点很不友好。
找了很久的资料都没找到合适的方法,比如按钮获取焦点时,让文本框重新得到焦点等。后面考虑到重新获取焦点的方式不可行!那么有没有办法让我点击按钮的时候不得到焦点,也就是说文本框至始至终都没有失去焦点呢?使用click事件不行,因为click事件触发时,输入框已经消失了,也就是文本框已经失去焦点了。经检测H5的touch事件是可以,比如touchend事件,阻止冒泡和默认行为,则按钮不会获取焦点。代码如下:
angular.element(document.querySelector("#send-button")).bind('touchend',function(e){
sendMessage();
e.stopPropagation();
e.preventDefault();
})
- ng-focus
探索的时候有用到这个事件,但是发现他在textarea元素上触发,button元素上竟然不触发,原来这个事件对元素有要求。<a>, <input>, <select>, <textarea>和 window 对象都支持该指令。
资料
- angular-elastic,几十行代码截止目前有671个star,只能说666呀。
- 如何做一个听话的 “输入框”