Better

Ethan的博客,欢迎访问交流

聊天输入框优化

最近开发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是只读属性
      
  • offsetLeft & offsetTop:得到对象的位置,到距离自身最近的(带有定位的)父元素的左侧/顶部 的距离,如果所有父级都没有定位则以body 为准
    • 区别和offset家族类似
  • scrollHeight & scrollWidth:对象内部实际内容的高度/宽度,包括内容区和内边距,不包括边框
  • scrollLeft & scrollTop:被卷去部分的顶部/左侧到可视区域顶部/左侧的距离
  • innerHeight & innerWidth:浏览器可见区域的内宽度、高度(不含浏览器的边框,但包含滚动条),一般用于window对象。
  • outerWidth & outerHeight:浏览器外宽度(包含浏览器的边框,因各个浏览器的边框边一样,得到的值也是不一样的)。

TEXTAREA实现

主要通过TEXTAREA结合angular-elastic实现,angular-elastic所做的事情就是当内容过多时自动增高,通过发布resize事件,这样一来对于ion-contention-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);
          });
        }
      }
    }])
    
  • 键盘事件
        //文本框聚焦 出现输入法
        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);
        });
    
    Bingo!完美

IOS输入框挡住

输入框位于底部时,键盘弹出时会看不见输入框,因为输入框被键盘挡住了,之前的解决办法比较简单干脆,但是如今发现其实很不友好,代码如下:

cordova.plugins.Keyboard.disableScroll(false);

但是这样设置存在问题,整个内容区域都会往上提,以至于标题都看不见了。

  • 为什么ios和android表现不一样?
    经过简单的测试,在android设备上,键盘出现时,整个ion-viewion-content的高度都会发生变化,而在ios设备上,键盘弹出和消失,ion-viewion-content的高度的并没有发生变化,键盘就好像浮在上方一样。问题原因出来了,解决办法也就来了,手动设置ion-content的高度和ion-footer-bar的bottom值(android会发生变化,无须设置bottom,ios必须设置),那么如何得到键盘的高度呢,通过cordova.plugins.Keyboar插件我们是可以得到高度的。
  • 有两个点需要注意:
    • IOSion-viewion-content的高度不发生变化,但是window.innerHeight发生变化
    • IOS的状态栏占了20px,需要多减去20px
  • 代码优化:
    .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);
          });
        }
      }
    }])
    
    键盘事件,e.keyboardHeight得到键盘高度
    //文本框聚焦 出现输入法
    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 对象都支持该指令。
    

资料



留言