zoukankan      html  css  js  c++  java
  • 关于sencha touch在华为、红米等部分手机下hide事件失效,msgbox无法关闭的解决方案(已更新最新解决方案)

     (急着解决问题的同学可以直接跳最底部查看最终的解决方案)

     问题描述

      因为前段时间抢到了华为荣耀3c,所以做项目的时候就用荣耀3c测试了一下项目,

      结果发现在华为的emotion ui上sencha touch的messagebox的弹窗,弹出后点击确认按钮时无法隐藏,

      有的圆角框还有会缺边,不过不仔细看倒是不看得出来,

      这是我的项目在手机上的截图,

      当我点击确定按钮的时候,messagebox的模态背景消失了,但是弹窗并不会消失,仔细看登陆框的圆角,有点缺边,我想华为应该是改过系统的浏览器内核了,至于做了哪些变动,这还真说不清

      

      对于圆角缺边,只能暂时无视了,但是弹窗不能消失的情况严重影响用户使用,

      在后来的测试中,发现了更为严重的bug,项目中所有组件的hide事件都不会触发,

      导致我在hide事件中手动进行销毁的全部失效了,

      而官方的例子运行起来也存在很多问题

    问题解析:

      为了找出问题的所在,

      首先,我下载了几款别人已经发布的sencha touch的apk进行了下测试,

      发现在emotion ui 2.0上都存在这些bug,无意中又发现魔狼在世很久之前做的 《迷尚豆捞》 竟然没问题,经魔狼本人确认是使用2.0版本的sencha touch进行开发的项目,

      于是我下载了从2.0版本到2.3.1版本的sencha touch的sdk进行了测试,

      最终发现从2.2.1版本开始都存在这个问题,

      我想应该可以通过代码解决这个问题,于是花费了大量的时间开始调试查看源码,对于这个在pc上完全没有问题,在手机自带的浏览器上才会出现的bug只能通过在android上用logcat查看console输出来和pc端的调试结果进行比对来找出差别了,

      通过大量的调试查找,最终被找到了问题所在,并且发现问题描述中的所有bug都是因为这个问题产生的,

      

      原来,当组件执行隐藏的时候会触发Component.js里的hide方法

      代码如下:

      hide: function(animation) {
            this.setCurrentAlignmentInfo(null);
            if(this.activeAnimation) {//激活的动画对象,相当于正在运行中的动画
                this.activeAnimation.on({
                    animationend: function(){
                        this.hide(animation);
                    },
                    scope: this,
                    single: true
                });
                return this;
            }
    
         //判断组件是否被隐藏,如果没有被隐藏通过setHidden(true)进行隐藏操作
    if (!this.getHidden()) { if (animation === undefined || (animation && animation.isComponent)) { animation = this.getHideAnimation(); } if (animation) { if (animation === true) { animation = 'fadeOut'; } this.onBefore({ hiddenchange: 'animateFn', scope: this, single: true, args: [animation] }); } this.setHidden(true);//进行隐藏操作,正常情况下,操作执行完,激活的动画运行完会被重置为null } return this; }

      当执行setHidden时会触发Evented.js里的设置方法并最终触发Componet.js里的animateFn方法,此方法会将activateAnimation重置为null,

      但是在华为的手机上并没有被重置,

      继续查看animateFn函数

      animateFn: function(animation, component, newState, oldState, options, controller) {
            var me = this;
            if (animation && (!newState || (newState && this.isPainted()))) {
    
                this.activeAnimation = new Ext.fx.Animation(animation);//给激活动画对象设置一个动画对象
                this.activeAnimation.setElement(component.element);
    
                if (!Ext.isEmpty(newState)) {
                    this.activeAnimation.setOnEnd(function() {
                        me.activeAnimation = null;//当动画结束的时候重置activateAnimation为null
                        controller.resume();
                    });
    
                    controller.pause();
                }
    
                Ext.Animator.run(me.activeAnimation);//运行动画
            }
        }

    在这个方法中我们看到activeAnimation绑定了end事件,在setOnEnd里将activateAnimation进行了重置,但是在emotion ui上却没有触发这段代码,

     于是继续往下查找,通过

    Ext.Animator.run(me.activeAnimation)

    我们进入动画执行阶段

    这里会调用到这下面的CssTransition.js里的run方法

    run的执行过程没有任何问题,关键问题就是这个js里有个onAnimationEnd方法,它在emotion ui上没有被触发,

    而这个方法是通过refreshRunningAnimationsData这个方法触发的

     1 refreshRunningAnimationsData: function(element, propertyNames, interrupt, replace) {
     2         var id = element.getId(),
     3             runningAnimationsData = this.runningAnimationsData,
     4             runningData = runningAnimationsData[id];
     5 
     6         if (!runningData) {
     7             return;
     8         }
     9 
    10         var nameMap = runningData.nameMap,
    11             nameList = runningData.nameList,
    12             sessions = runningData.sessions,
    13             ln, j, subLn, name,
    14             i, session, map, list,
    15             hasCompletedSession = false;
    16 
    17         interrupt = Boolean(interrupt);
    18         replace = Boolean(replace);
    19 
    20         if (!sessions) {
    21             return this;
    22         }
    23 
    24         ln = sessions.length;
    25 
    26         if (ln === 0) {
    27             return this;
    28         }
    29 
    30         if (replace) {
    31             runningData.nameMap = {};
    32             nameList.length = 0;
    33 
    34             for (i = 0; i < ln; i++) {
    35                 session = sessions[i];
    36                 this.onAnimationEnd(element, session.data, session.animation, interrupt, replace);
    37             }
    38 
    39             sessions.length = 0;
    40         }
    41         else {
    42             for (i = 0; i < ln; i++) {
    43                 session = sessions[i];
    44                 map = session.map;
    45                 list = session.list;
    46 
    47                 for (j = 0,subLn = propertyNames.length; j < subLn; j++) {
    48                     name = propertyNames[j];
    49 
    50                     if (map[name]) {//当执行transform的时候这里传过来的name是-webkit-transform,但是map里只有transform属性,问题就出在这里,匹配不一致导致动画不会被移除
    51                         delete map[name];//动画存在移除匹配的动画属性
    52                         Ext.Array.remove(list, name);
    53                         session.length--;//因为map不匹配,导致少执行一次session.length--,session.length永远不为0
    54                         if (--nameMap[name] == 0) {
    55                             delete nameMap[name];
    56                             Ext.Array.remove(nameList, name);
    57                         }
    58                     }
    59                 }
    60 
    61                 if (session.length == 0) {//当动画移除完毕时执行
    62                     sessions.splice(i, 1);
    63                     i--;
    64                     ln--;
    65 
    66                     hasCompletedSession = true;
    67                     this.onAnimationEnd(element, session.data, session.animation, interrupt);//触发动画结束事件,最终组件被隐藏,hide事件被触发
    68                 }
    69             }
    70         }
    71 
    72         if (!replace && !interrupt && sessions.length == 0 && hasCompletedSession) {
    73             this.onAllAnimationsEnd(element);
    74         }
    75     }

    问题就出在上面代码第50行的判断那里,

    propertyNames对应的是从onTransitionEnd方法里传过来的e.browserEvent.propertyName参数

    sencha touch里的这个browserEvent封装的是浏览器的原生对象,当执行到css的transform时候,这个propertyName对应的是"-webkit-transform",

    而map对象里保存的是run方法里传的目标动画的相关内容,map里却是transform属性,因为匹配不对,导致session.length--少执行一次,session.length永远不为0,

    所以后面的onAnimationEnd即动画结束的方法永远不被触发,

    然后Msgbox也就不会隐藏了,同时,所有的hide事件也没有被触发,

    为什么会不匹配呢,

    我们往上查找,

    原来最终问题是在run方法里导致的

      1 run: function(animations) {
      2         var me = this,
      3             isLengthPropertyMap = this.lengthProperties,
      4             fromData = {},
      5             toData = {},
      6             data = {},
      7             element, elementId, from, to, before,
      8             fromPropertyNames, toPropertyNames,
      9             doApplyTo, message,
     10             runningData, elementData,
     11             i, j, ln, animation, propertiesLength, sessionNameMap,
     12             computedStyle, formattedName, name, toFormattedValue,
     13             computedValue, fromFormattedValue, isLengthProperty,
     14             runningNameMap, runningNameList, runningSessions, runningSession;
     15 
     16         if (!this.listenersAttached) {
     17             this.attachListeners();
     18         }
     19 
     20         animations = Ext.Array.from(animations);
     21 
     22         for (i = 0,ln = animations.length; i < ln; i++) {
     23             animation = animations[i];
     24             animation = Ext.factory(animation, Ext.fx.Animation);
     25             element = animation.getElement();
     26 
     27             // Empty function to prevent idleTasks from running while we animate.
     28             Ext.AnimationQueue.start(Ext.emptyFn, animation);
     29 
     30             computedStyle = window.getComputedStyle(element.dom);
     31 
     32             elementId = element.getId();
     33 
     34             data = Ext.merge({}, animation.getData());
     35 
     36             if (animation.onBeforeStart) {
     37                 animation.onBeforeStart.call(animation.scope || this, element);
     38             }
     39             animation.fireEvent('animationstart', animation);
     40             this.fireEvent('animationstart', this, animation);
     41 
     42             data[elementId] = data;
     43 
     44             before = data.before;
     45             from = data.from;
     46             to = data.to;
     47 
     48             data.fromPropertyNames = fromPropertyNames = [];
     49             data.toPropertyNames = toPropertyNames = [];
     50 
     51             for (name in to) {
     52                 if (to.hasOwnProperty(name)) {
     53                     to[name] = toFormattedValue = this.formatValue(to[name], name);
     54                     formattedName = this.formatName(name);//这里就是出问题的地方,传进去的name是transform,这个formatName就是判断你的浏览器属性然后对这个那么进行前缀添加
     55                     isLengthProperty = isLengthPropertyMap.hasOwnProperty(name);
     56 
     57                     if (!isLengthProperty) {
     58                         toFormattedValue = this.getCssStyleValue(formattedName, toFormattedValue);
     59                     }
     60 
     61                     if (from.hasOwnProperty(name)) {
     62                         from[name] = fromFormattedValue = this.formatValue(from[name], name);
     63 
     64                         if (!isLengthProperty) {
     65                             fromFormattedValue = this.getCssStyleValue(formattedName, fromFormattedValue);
     66                         }
     67 
     68                         if (toFormattedValue !== fromFormattedValue) {
     69                             fromPropertyNames.push(formattedName);
     70                             toPropertyNames.push(formattedName);
     71                         }
     72                     }
     73                     else {
     74                         computedValue = computedStyle.getPropertyValue(formattedName);
     75 
     76                         if (toFormattedValue !== computedValue) {
     77                             toPropertyNames.push(formattedName);
     78                         }
     79                     }
     80                 }
     81             }
     82 
     83             propertiesLength = toPropertyNames.length;
     84 
     85             if (propertiesLength === 0) {
     86                 this.onAnimationEnd(element, data, animation);
     87                 continue;
     88             }
     89 
     90             runningData = this.getRunningData(elementId);
     91             runningSessions = runningData.sessions;
     92 
     93             if (runningSessions.length > 0) {
     94                 this.refreshRunningAnimationsData(
     95                     element, Ext.Array.merge(fromPropertyNames, toPropertyNames), true, data.replacePrevious
     96                 );
     97             }
     98 
     99             runningNameMap = runningData.nameMap;
    100             runningNameList = runningData.nameList;
    101 
    102             sessionNameMap = {};
    103             for (j = 0; j < propertiesLength; j++) {
    104                 name = toPropertyNames[j];
    105                 sessionNameMap[name] = true;
    106 
    107                 if (!runningNameMap.hasOwnProperty(name)) {
    108                     runningNameMap[name] = 1;
    109                     runningNameList.push(name);
    110                 }
    111                 else {
    112                     runningNameMap[name]++;
    113                 }
    114             }
    115 
    116             runningSession = {
    117                 element: element,
    118                 map: sessionNameMap,
    119                 list: toPropertyNames.slice(),
    120                 length: propertiesLength,
    121                 data: data,
    122                 animation: animation
    123             };
    124             runningSessions.push(runningSession);
    125 
    126             animation.on('stop', 'onAnimationStop', this);
    127 
    128             elementData = Ext.apply({}, before);
    129             Ext.apply(elementData, from);
    130 
    131             if (runningNameList.length > 0) {
    132                 fromPropertyNames = Ext.Array.difference(runningNameList, fromPropertyNames);
    133                 toPropertyNames = Ext.Array.merge(fromPropertyNames, toPropertyNames);
    134                 elementData['transition-property'] = fromPropertyNames;
    135             }
    136 
    137             fromData[elementId] = elementData;
    138             toData[elementId] = Ext.apply({}, to);
    139 
    140             toData[elementId]['transition-property'] = toPropertyNames;
    141             toData[elementId]['transition-duration'] = data.duration;
    142             toData[elementId]['transition-timing-function'] = data.easing;
    143             toData[elementId]['transition-delay'] = data.delay;
    144 
    145             animation.startTime = Date.now();
    146         }
    147 
    148         message = this.$className;
    149 
    150         this.applyStyles(fromData);
    151 
    152         doApplyTo = function(e) {
    153             if (e.data === message && e.source === window) {
    154                 window.removeEventListener('message', doApplyTo, false);
    155                 me.applyStyles(toData);
    156             }
    157         };
    158 
    159         if(Ext.browser.is.IE) {
    160             window.requestAnimationFrame(function() {
    161                 window.addEventListener('message', doApplyTo, false);
    162                 window.postMessage(message, '*');
    163             });
    164         }else{
    165             window.addEventListener('message', doApplyTo, false);
    166             window.postMessage(message, '*');
    167         }
    168     }

    54行的formatName这个方法是对浏览器进行css判断然后给传进去的name参数加上浏览器前缀,

    最终回传给formattedName,而这个formattedName最终会对应到map里的属性,

    但是这个formatName在emotion Ui上的判断跟预期不一样

    我们看一下formatName的方法

     1 formatName: function(name) {
     2         var cache = this.formattedNameCache,
     3             formattedName = cache[name];
     4 
     5         if (!formattedName) {
     6             if ((Ext.os.is.Tizen || !Ext.feature.has.CssTransformNoPrefix) && this.prefixedProperties[name]) {//Ext.feature.has.CssTransformNoPrefix判断
     7                 formattedName = this.vendorPrefix + name;                               //结果相反了,导致执行了else里的代码,将transform
     8             }                                                         //在没有加前缀的情况下返回了回去
     9             else {
    10                 formattedName = name;
    11             }
    12 
    13             cache[name] = formattedName;
    14         }
    15 
    16         return formattedName;
    17     }

    如上所示,在判断Ext.feature.has.CssTransformNoPrefix的时候预期结果跟实际相反了,

    emotion ui自带浏览器判断的结果是true,但实际上应该为false,

    于是导致执行了下面else里的代码,

    name参数transform传了进来,没有加上前缀又以transform传了回去,本应该传-webkit-transform的

    但是在后来的判断中原生event对象里的propertyName又是加前缀的,

    于是导致了refreshRunningAnimationsData里map["-webkit-transform"]匹配不一致,

    代码判断不对,于是session.length--少执行一次,

    session.length不会为0就不会触发后面的onAnimationEnd方法了,最终,

    组件没有被隐藏,hide事件没有被触发,

    那老版本的sencha touch为什么没这个问题,

    因为老版本没有对

    (Ext.os.is.Tizen || !Ext.feature.has.CssTransformNoPrefix)

    所以老版本没有出现这个问题,

    在其他的android系统上,Ext.feature.has.CssTransformNoPrefix这个值都是false,即不支持没有前缀,

    包括最新版本的chrome,但是在华为emotion ui上这个判断不对了,原因是什么,我也不清楚,

    最终解决方案:

    由于sencha touch对css前缀判断有些问题,所以最终我修改了touch/src/fx/runner/CssTransition.js中的源码,

    因为前缀不匹配,所以我将浏览器自带事件的propertyName做了处理,以保证前缀一致,

    修改文件中onTransitionEnd方法如下:

    onTransitionEnd: function (e) {
            var target = e.target,
                id = target.id,
                propertyName = e.browserEvent.propertyName,
                styleDashPrefix = Ext.browser.getStyleDashPrefix();
            if (id && this.runningAnimationsData.hasOwnProperty(id)) {
                if (Ext.feature.has.CssTransformNoPrefix) {
                    if (propertyName.indexOf(styleDashPrefix) >= 0) {
                        propertyName = propertyName.substring(styleDashPrefix.length);
                    }
                }
                this.refreshRunningAnimationsData(Ext.get(target), [propertyName]);
            }
        }
    

      

  • 相关阅读:
    每天一道leetcode 搜索旋转排序数组(二分法)
    每天一道leetcode 统计重复个数(循环节)
    python3 简单web目录扫描脚本(后续更新完整)
    每天一道leetcode 盛最多水的容器 (双指针)
    python3 语法学习 类和继承
    python3 语法学习 文件操作及os方法
    python3 语法学习 输入输出美观
    TCP/IP 协议:IP 协议
    TCP/IP 协议:链路层概述
    Http权威指南(二)---读书笔记
  • 原文地址:https://www.cnblogs.com/cjpx00008/p/3535557.html
Copyright © 2011-2022 走看看