Vue, App与我(十四)

Vue, App与我(十四)

前言:

  • Big-man的PHP老师在今天调试程序的时候,出现了一些Bug, 也就是基于微信公众号开发的项目程序,在打开其中的链接的时候,微信中显示是正常的,但是在微信选择基于浏览器打开的时候也就是出现Bug, 这个Bug只会在Android机型的一部分浏览器中进行使用时候会出现这样的情况, 这导致Bug-man十分地困惑与不解。
  • 其中最令Big-man不解的就是为什么IOS的机型打开没有任何的问题, 在部分的Android机型中解析不出来。

Bug代码获取:

<input type="text" placeholder="请输入..." v-model.trim="username" v-bind:value="username" />
<script>
    data() {
        return {
            username: '',
        }
    }
</script>
  • v-modelv-bind:value不能在同一段代码中进行使用,这样的结论是Big-man的PHP老师在查找过程总结得出的,但是显然这样的结论并不能说服Big-man内心的疑虑。Big-man需要静下心来进行分析。

表单输入绑定

  • 这是Vue.js(2.0)中提到的一个概念, 英文也就是v-modelv-model指令在表单控件元素上创建双向数据绑定, 它会根据空间类型自动选取正确的方法来更新元素。
  • 负责监听用户的输入事件以更新数据, 并特别处理一些极端的例子。
  • v-model会忽略所有表单元素的valuecheckedselected特性的初始化值。因为它会选择Vue实例数据来作为具体的值。应该通过JavaScript在组件的data选项中声明初始值。
  • 对于要求IME(如中文、日语、韩语等)(IME意为”输入法”)的语言, 你会发现v-model不会在ime输入中得到更新。如果大家也想实现更新, 请使用input事件。

重要的一句话:

  • v-model会忽略所有表单元素的valuecheckedselected特性的初始化值
  • 所以在分类使用中,各自之间的处理是不一样的。
  • 以下特例希望在实际例子中去测试。

处理文本:

<input v-model="message" placeholder="edit me">
<p>Message is: {{ message }}</p>

处理多行文本:

<span>Multiline message is:</span>
<p style="white-space: pre-line;">{{ message }}</p>
<br>
<textarea v-model="message" placeholder="add multiple lines"></textarea>
  • 值得注意的是: 在文本区域插值 (<textarea></textarea>) 并不会生效,应用 v-model 来代替。

复选框:

  • 单个勾选框, 逻辑值:
<input type="checkbox" id="checkbox" v-model="checked">
<label for="checkbox">{{ checked }}</label>
  • 多个复选框, 绑定到同一个数组:
<div id='example-3'>
  <input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
  <label for="jack">Jack</label>
  <input type="checkbox" id="john" value="John" v-model="checkedNames">
  <label for="john">John</label>
  <input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
  <label for="mike">Mike</label>
  <br>
  <span>Checked names: {{ checkedNames }}</span>
</div>
new Vue({
  el: '#example-3',
  data: {
    checkedNames: []
  }
})

单选按钮:

<div id="example-4">
  <input type="radio" id="one" value="One" v-model="picked">
  <label for="one">One</label>
  <br>
  <input type="radio" id="two" value="Two" v-model="picked">
  <label for="two">Two</label>
  <br>
  <span>Picked: {{ picked }}</span>
</div>
new Vue({
  el: '#example-4',
  data: {
    picked: ''
  }
})

选择列表:

  • 单选列表:
<div id="example-5">
  <select v-model="selected">
    <option disabled value="">请选择</option>
    <option>A</option>
    <option>B</option>
    <option>C</option>
  </select>
  <span>Selected: {{ selected }}</span>
</div>
new Vue({
  el: '...',
  data: {
    selected: ''
  }
})
  • 请注意: 如果 v-model 表达初始的值不匹配任何的选项,<select> 元素就会以”未选中”的状态渲染。在 iOS 中,这会使用户无法选择第一个选项,因为这样的情况下,iOS 不会引发 change 事件。因此,像以上提供 disabled 选项是建议的做法。

  • 多选列表 (绑定到一个数组):

<div id="example-6">
  <select v-model="selected" multiple style="width: 50px;">
    <option>A</option>
    <option>B</option>
    <option>C</option>
  </select>
  <br>
  <span>Selected: {{ selected }}</span>
</div>
new Vue({
  el: '#example-6',
  data: {
    selected: []
  }
})
  • 动态选项,用 v-for 渲染:
<select v-model="selected">
  <option v-for="option in options" v-bind:value="option.value">
    {{ option.text }}
  </option>
</select>
<span>Selected: {{ selected }}</span>
new Vue({
  el: '...',
  data: {
    selected: 'A',
    options: [
      { text: 'One', value: 'A' },
      { text: 'Two', value: 'B' },
      { text: 'Three', value: 'C' }
    ]
  }
})

值绑定:

  • 对于单选按钮勾选框选择列表选项v-model 绑定的 value 通常是静态字符串 (对于勾选框是逻辑值):
<!-- 当选中时,`picked` 为字符串 "a" -->
<input type="radio" v-model="picked" value="a">
<!-- `toggle` 为 true 或 false -->
<input type="checkbox" v-model="toggle">
<!-- 当选中时,`selected` 为字符串 "abc" -->
<select v-model="selected">
  <option value="abc">ABC</option>
</select>
  • 但是有时我们想绑定 valueVue 实例的一个动态属性上,这时可以用 v-bind 实现,并且这个属性的值可以不是字符串,Big-man理解的就是这样的处理满足的属性值的类型丰富了。

复选框:

<input
  type="checkbox"
  v-model="toggle"
  v-bind:true-value="a"
  v-bind:false-value="b"
>
// 当选中时
vm.toggle === vm.a
// 当没有选中时
vm.toggle === vm.b

单选按钮:

<input type="radio" v-model="pick" v-bind:value="a">
// 当选中时
vm.pick === vm.a
  • 所以Big-man的这位PHP老师总结得不是很到位, 并不是说input所有的type都不允许v-modelv-bind:value同时出现, 只不过是type的某些属性值会出现这种Bug的现象。

修饰符:

.lazy:

  • 在默认情况下,v-modelinput 事件中同步输入框的值与数据 (除了 上述 IME 部分),但你可以添加一个修饰符 lazy ,从而转变为在 change 事件中同步
<!-- 在 "change" 而不是 "input" 事件中更新 -->
<input v-model.lazy="msg" >

.number:

  • 如果想自动将用户的输入值转为 Number 类型 (如果原值的转换结果为 NaN 则返回原值),可以添加一个修饰符 numberv-model 来处理输入值:
<input v-model.number="age" type="number">
  • 这通常很有用,因为在 type="number" 时 HTML 中输入的值也总是会返回字符串类型

.trim:

  • 如果要自动过滤用户输入的首尾空格,可以添加 trim 修饰符到 v-model 上过滤输入:
<input v-model.trim="msg">

Android和iOS的理解不一致:

  • 首先来解释一下它们的不同点,看从不同点出发能不能找到问题的切入口:

iOS:

  • iOS自带的是safari浏览器:
  • safari不支持fixed, input输入框;
  • 例如如下代码:
<body>
    <header>
    </header>
    <main>
    </main>
    <footer>
        <input type="text" placeholder="Footer..." />
        <button class="submit">提交</button>
    </footer>
</body>
  • header使用fixed定义的头部(相对定位布局);(display:flex才是流式布局,Big-man处理失误了)
  • main中是主要内容部分, main区域是可以滚动的区域;
  • footer使用fixed定义的底部(流式布局);
  • 看如下CSS代码:
header, main, footer {
    display: block;
}
header {
    position: fixed;
    height: 50px;
    top: 0;
    left: 0;
    right: 0;
}
main {
    height: 2000px;
    margin-top: 50px;
    margin-bottom: 34px;
}
footer {
    position: fixed;
    height: 34px;
    bottom: 0;
    left: 0;
    right: 0;
}
  • 拖动页面时headerfooter已经定位在了对应的位置, 目测没问题了。如下图所示:
    正常显示
  • 但接下来问题就来了! 如果底部输入框软键盘被唤起以后, 再次滑动页面, 就会看到如下图所示:
    不正常显示
  • fixed属性定位好的页面元素随页面滚动起来… fixed的属性定位宣告失败!Big-man在想这是为什么了?
  • 软键盘唤起后, 页面的fixed元素将失效(即无法浮动, 也可以理解为变成absolute定位), 所以当页面超过一屏且滚动时, 失效的fixed元素就会跟随滚动了。
  • 这也就是iOSfixed元素和输入框(input)的Bug。其中不仅限于type = text 的输入框, 凡是软键盘(比如时间选择select选择等等)被唤起, 都会遇到同样地问题。
  • 虽然isScoll.js可以很好的解决fixed定位滚动的问题, 但是不在万不得已的情况下, 尽量去做到不依赖于第三方库的布局方案, 以简化实现方式,Big-man就是不想去依赖于第三方库,所以最开始的评论框跳转到下一页就行评论。

iOS的fixed解决思路:

  • Big-man这里提出一种思考的思路,具体实现如何,Big-man就不敢保证了。思路如下:
  • 既然在iOS下由于软键盘触发后, 页面的fixed元素会失效, 导致跟随页面一起滚动, 那么假如一页页面不会过长出现滚动的效果,那么即便fixed元素失效, 也无法跟随页面滚动, 也就不会出现上面的情况了。
  • 那么按照这个思路, 如果使fixed元素的父级不出现滚动, 而将原来body滚动的区域移动到main内部, 而headerfooter的样式不变。代码修改如下:
<body class="layout-scroll-fixed">
    <header>
    </header>
    <main>
        <div class="content">

        </div>
    </main>
    <footer>
        <input type="text" placeholder="Footer..." />
        <button class="submit">提交</button>
    </footer>
</body>
  • header: position定位为fixed或者absolute;
  • main: 可以滚动的区域以及主要内容在这个区域中的;
  • footer: position定位为fixed或者absolute;
header, footer, main {
    display: block;
}
header {
    position: fixed; // 当然absolute也没有意见
    height: 50px;
    top: 0;
    left: 0;
    right: 0;
}
main {
    position: absolute; // 当然这里就不能是fixed, 因为main区域是需要在内部进行滚动的.
    top: 50px;
    bottom: 34px;
    overflow-y: scroll; //垂直的Y轴可以进行滚动
    -webkit-overflow-scrolling: touch; // 这个属性可以增加弹性, 滑动可以更加顺畅
}
main .content {
    height: 2000px;
}
footer {
    position: fixed; // 当然absolute也没有意见
    height: 34px;
    bottom: 0;
    left: 0;
    right: 0;
}
  • 另外,这里的 headerfooter 使用的是 fixed 定位,如果考虑到更老一些的 iOS(比如iOS 9以前) 系统不支持 fixed 元素,完全可以把 fixed 替换成 absolute 。测试后效果是一样的。

safari图片加载失败, 默认图片过大:

  • 网站当中经常会遇到图片加载失败的问题,img中有地址,但是地址打开是错误的。情况如下:
Notice: Undefined index: iBID in /home/data/www/hyb/file/app/Bll/File.php on line 265 
404 Not Found
  • 不同浏览器处理错误图片是不一样的,有的干脆就显示差号,例如IE,有的显示一张破碎的图片,有的则是给一张高度比较大的默认图,例如PC端的火狐IOS中Safari,还有安卓中的UC浏览器。这样在手机中就会导致左右两侧图片高度不一致!如下图:
    img

图片加载失败的解决方案:

  • 其实这里解决很简单,判断当图片加载失败的时候给一个默认图就可以了,不让浏览器使用其自带的默认图。
<img src="http://x.xytywlkj.com/pic/20170626/product//1498448745PZCEs.jpg"
onerror="javascript:this.src='http://x.xytywlkj.com/pic/20170626/product//1498448745PZCEs.jpg';" alt="pic" />

图片加载失败出现的衍生问题:

  • 因为图片加载失败进入默认图,那么默认图再加载失败怎么办呢?这不是进入一个死循环吗?
  • 最简单的一个解决办法是,onerror中的图保证能打开保证比较小不会出现问题!这个方法也是最有效的方法
  • 但是很多的都是我并不能保证这个图片不出现问题, Big-man也就只能给出相应的解决方案了。
  • 方案的解决思路如下:
  • 当图片加载失败, 进入onerror的时候, 判断onerror的图片是不是能加载, 在onerror中的图片触发onerror的时候, 设置onerrornull
<img src="http://x.xytywlkj.com/pic/20170626/product/small/1498465778hhYzV.jpg!640.398" onerror="nofind();" />
<script type="text/javascript">
    function nofind() {
        // 获取img对象, `Mozilla Firefox`是`event.target`, `IE`和`Chrome`是`event.srcElement`
        var img = event.srcElement || event.target;
        console.dir(img); // 大家可以像Big-man这样打印出来看看
        img.src = "http://x.xytywlkj.com/pic/20170626/product/small/1498465778hhYzV.jpg!320.210";
        img.onerror = null;
    }
</script>

Android的UC浏览器:

  • 安卓UC为代表的浏览器不支持部分CSS3属性, 例如calcwidth:90%;width: calc(ssadft);
  • 滚动事件不会处触发touchmove事件;

手机浏览器通用问题:

  • 弹出层touchmove滚动, 会触发touch滚动(出现前提是body中有滚动轴)。
  • 手机网站表层div滑动导致底层body滑动touchmove的阻止)。
  • body很长,可以滑动,body头部有一个模拟下拉的选择框。下拉选择有滚动轴,如下图:
    如下图
  • Big-man给body一个overflow:hidden高度没有用的。手机网站上背景还是可以滑动,然后Big-man给body一个touchmovepreventdefault()阻止事件,body滑动阻止了,PC上面是可以了,但是手机上面滑动div还是会导致底部body的滑动,Big-man给div 一个阻止冒泡的事件stopPropagation()手机网站上面还是不可以

解决方案:

  • Big-man经过反复测试, 发现在经过轴滚动底部滚动的时候, 会触发body的滑动, 那么就需要在事件滚动到底部的时候对表层div做一个touchmove的阻止。到达滚动轴底部, 向下滑动, 阻止事件, 向上滑动开启事件。为此就要判断touchmove的方向。
var startX, startY;
$("body").on("touchstart", function(e) {
    e.preventDefault();
    startX = e.originalEvent.changedTouches[0].pageX,
    startY = e.originalEvent.changedTouches[0].pageY;
});
$("body").on("touchmove", function(e) {
    e.preventDefault();
    var moveEndX = e.originalEvent.changedTouches[0].pageX,
    moveEndY = e.originalEvent.changedTouches[0].pageY,
    X = moveEndX - startX,
    Y = moveEndY - startY;

    if ( Math.abs(X) > Math.abs(Y) && X > 0 ) {
        alert("left to right");
    }
    else if ( Math.abs(X) > Math.abs(Y) && X < 0 ) {
        alert("right to left");
    }
    else if ( Math.abs(Y) > Math.abs(X) && Y > 0) {
        alert("top to bottom");
    }
    else if ( Math.abs(Y) > Math.abs(X) && Y < 0 ) {
        alert("bottom to top");
    }
    else {
        alert("just touch");
    }
});
  • 以上的方法是判断touchmove的滑动方向。
  • 除了上面方法判断手机端手机滑动方向,这里还会再介绍一个方案,就是封装一个角度函数(Angle Function),通过角度函数来判断也还不错!这里仅仅把这种方式实现上滑下滑左滑右滑列举一下!
    var startx, starty;
    //获得角度
    function getAngle(angx, angy) {
        return Math.atan2(angy, angx) * 180 / Math.PI;
    };

    //根据起点终点返回方向 1向上 2向下 3向左 4向右 0未滑动
    function getDirection(startx, starty, endx, endy) {
        var angx = endx - startx;
        var angy = endy - starty;
        var result = 0;

        //如果滑动距离太短
        if (Math.abs(angx) < 2 && Math.abs(angy) < 2) {
            return result;
        }

        var angle = getAngle(angx, angy);
        if (angle >= -135 && angle <= -45) {
            result = 1;
        } else if (angle > 45 && angle < 135) {
            result = 2;
        } else if ((angle >= 135 && angle <= 180) || (angle >= -180 && angle < -135)) {
            result = 3;
        } else if (angle >= -45 && angle <= 45) {
            result = 4;
        }
        return result;
    }
    //手指接触屏幕
    document.addEventListener("touchstart", function(e) {
        startx = e.touches[0].pageX;
        starty = e.touches[0].pageY;
    }, false);
    //手指离开屏幕
    document.addEventListener("touchend", function(e) {
        var endx, endy;
        endx = e.changedTouches[0].pageX;
        endy = e.changedTouches[0].pageY;
        var direction = getDirection(startx, starty, endx, endy);
        switch (direction) {
            case 0:
                alert("未滑动!");
                break;
            case 1:
                alert("向上!")
                break;
            case 2:
                alert("向下!")
                break;
            case 3:
                alert("向左!")
                break;
            case 4:
                alert("向右!")
                break;
            default:
        }
    }, false);
  • 知道滑动方向(上滑下滑左滑右滑)如何判断,那么解决这个问题我们可以判断是否滑动到底部或者顶部,假如滑动到底部再往下滑动,就阻止滑动往上滑动,就开启滑动!滑动到顶部一个道理!总结代码如下:
    $('#footer').bind("touchmove", function (e) {
        e.preventDefault(); 
    }); // 底层
    $(".moveFather").bind("touchstart", function (events) {
        startY = events.originalEvent.changedTouches[0].pageY;
    });// 滚动的父亲层
    $(".moveFather ul").bind("touchmove", function (e) {
        var ulheight = $(this).height();
        var scrollTop = $(this).scrollTop();
        var scrollheight = $(this)[0].scrollHeight;
        if (ulheight + scrollTop + 20 >= scrollheight) { 
            //滚到底部20px左右
            $(".moveFather").bind("touchmove", function(event){                        
                moveEndY = 
                event.originalEvent.changedTouches[0].pageY;
                theY = moveEndY - startY;
                if(Math.abs(theY) > Math.abs(theX)&&theY > 0){ 
                    //用上面的abs()更加准确!这里是判断上滑还是下滑!可以用角度函数也可以用上面绝对值方式!
                   $(".滚动的父亲").unbind("touchmove");
                   //滑动到底部再往上滑动,解除阻止!
                }
                if (Math.abs(theY) > Math.abs(theX) && theY < 0) {
                    event.preventDefault();
                    //滑动到底部,再往下滑动,阻止滑动!
                }
          })
    }
    if (scrollTop < 20) {
        //滚到顶部20px左右
        $(".moveFather").bind("touchmove", function (event) {
          moveEndY = 
          event.originalEvent.changedTouches[0].pageY,
          theY = moveEndY - startY;
          if (Math.abs(theY) > Math.abs(theX) && theY > 0) {
              event.preventDefault();
          }
          if (Math.abs(theY) > Math.abs(theX) && theY < 0) {
              $(".moveFather").unbind("touchmove");
          }
       })
    }
});
  • 以上方法基本上能够阻止body的滚动, 但是在移动端的使用上总是存在不同的效果, 所以期待有一种更加全面的解决方案。

张鑫旭给出的一种解决方案:

<aside id="aside" class="aside">
    <i class="aside-overlay hideAside"></i>
    <div class="aside-content">
        <div class="module module-filter-list">
            <div class="module-main scrollable">
                <ul id="filters" class="sort-ul">
                    .......
                </ul>
            </div>
        </div>
    </div>
</aside>
.noscroll,
.noscroll body {
    overflow: hidden;
}
.noscroll body {
    position: relative;
}
$.smartScroll = function(container, selectorScrollable) {
    // 如果没有滚动容器选择器,或者已经绑定了滚动时间,忽略
    if (!selectorScrollable || container.data('isBindScroll')) {
        return;
    }

    // 是否是搓浏览器
    // 自己在这里添加判断和筛选
    var isSBBrowser;

    var data = {
        posY: 0,
        maxscroll: 0
    };

    // 事件处理
    container.on({
        touchstart: function (event) {
            var events = event.touches[0] || event;

            // 先求得是不是滚动元素或者滚动元素的子元素
            var elTarget = $(event.target);

            if (!elTarget.length) {
                return;    
            }

            var elScroll;

            // 获取标记的滚动元素,自身或子元素皆可
            if (elTarget.is(selectorScrollable)) {
                elScroll = elTarget;
            } else if ((elScroll = elTarget.parents(selectorScrollable)).length == 0) {
                elScroll = null;
            }

            if (!elScroll) {
                return;
            }

            // 当前滚动元素标记
            data.elScroll = elScroll;

            // 垂直位置标记
            data.posY = events.pageY;
            data.scrollY = elScroll.scrollTop();
            // 是否可以滚动
            data.maxscroll = elScroll[0].scrollHeight - elScroll[0].clientHeight;
        },
        touchmove: function () {
            // 如果不足于滚动,则禁止触发整个窗体元素的滚动
            if (data.maxscroll <= 0 || isSBBrowser) {
                // 禁止滚动
                event.preventDefault();
            }
            // 滚动元素
            var elScroll = data.elScroll;
            // 当前的滚动高度
            var scrollTop = elScroll.scrollTop();

            // 现在移动的垂直位置,用来判断是往上移动还是往下
            var events = event.touches[0] || event;
            // 移动距离
            var distanceY = events.pageY - data.posY;

            if (isSBBrowser) {
                elScroll.scrollTop(data.scrollY - distanceY);
                elScroll.trigger('scroll');
                return;
            }

            // 上下边缘检测
            if (distanceY > 0 && scrollTop == 0) {
                // 往上滑,并且到头
                // 禁止滚动的默认行为
                event.preventDefault();
                return;
            }

            // 下边缘检测
            if (distanceY < 0 && (scrollTop + 1 >= data.maxscroll)) {
                // 往下滑,并且到头
                // 禁止滚动的默认行为
                event.preventDefault();
                return;
            }
        },
        touchend: function () {
            data.maxscroll = 0;
        }    
    });

    // 防止多次重复绑定
    container.data('isBindScroll', true);
};
  • 使用:
$('#aside').addClass('active');
$.smartScroll($('#aside'), '.scrollable');
$('html').addClass('noscroll');

字体:

  • 假如整个页面用rem字体大小控制的话, 部分Android机型的手机上会出现字体过大的情况;
    • 这个时候只需要给字体元素的父元素增加一个font-size属性, 然后再去设置字体元素的font-size属性就没问题了。

边距:

  • 存在一部分地的Android手机对于margin的属性比较严格, 这就叫做外边距, 但是可以使用内边距进行处理。

JackDan9 Thinking

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值