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-model
与v-bind:value
不能在同一段代码中进行使用,这样的结论是Big-man的PHP老师在查找过程总结得出的,但是显然这样的结论并不能说服Big-man内心的疑虑。Big-man需要静下心来进行分析。
表单输入绑定
- 这是Vue.js(2.0)中提到的一个概念, 英文也就是
v-model
,v-model
指令在表单控件元素上创建双向数据绑定, 它会根据空间类型自动选取正确的方法来更新元素。 - 负责监听用户的输入事件以更新数据, 并特别处理一些极端的例子。
v-model
会忽略所有表单元素的value
、checked
、selected
特性的初始化值。因为它会选择Vue实例数据来作为具体的值。应该通过JavaScript在组件的data
选项中声明初始值。- 对于要求
IME
(如中文、日语、韩语等)(IME意为”输入法”)的语言, 你会发现v-model
不会在ime
输入中得到更新。如果大家也想实现更新, 请使用input
事件。
重要的一句话:
v-model
会忽略所有表单元素的value
、checked
、selected
特性的初始化值。- 所以在分类使用中,各自之间的处理是不一样的。
- 以下特例希望在实际例子中去测试。
处理文本:
<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>
- 但是有时我们想绑定
value
到Vue
实例的一个动态属性上,这时可以用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-model
与v-bind:value
同时出现, 只不过是type
的某些属性值会出现这种Bug的现象。
修饰符:
.lazy
:
- 在默认情况下,
v-model
在input
事件中同步输入框的值与数据 (除了 上述IME
部分),但你可以添加一个修饰符lazy
,从而转变为在 change 事件中同步:
<!-- 在 "change" 而不是 "input" 事件中更新 -->
<input v-model.lazy="msg" >
.number
:
- 如果想自动将用户的输入值转为
Number
类型 (如果原值的转换结果为NaN
则返回原值),可以添加一个修饰符number
给v-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;
}
- 拖动页面时
header
和footer
已经定位在了对应的位置, 目测没问题了。如下图所示:
- 但接下来问题就来了! 如果底部输入框软键盘被唤起以后, 再次滑动页面, 就会看到如下图所示:
fixed
属性定位好的页面元素随页面滚动起来…fixed
的属性定位宣告失败!Big-man在想这是为什么了?- 软键盘唤起后, 页面的
fixed
元素将失效(即无法浮动, 也可以理解为变成absolute
定位), 所以当页面超过一屏且滚动时, 失效的fixed
元素就会跟随滚动了。 - 这也就是
iOS
上fixed
元素和输入框(input
)的Bug。其中不仅限于type = text 的输入框, 凡是软键盘(比如时间选择、select
选择等等)被唤起, 都会遇到同样地问题。 - 虽然
isScoll.js
可以很好的解决fixed
定位滚动的问题, 但是不在万不得已的情况下, 尽量去做到不依赖于第三方库的布局方案, 以简化实现方式,Big-man就是不想去依赖于第三方库,所以最开始的评论框跳转到下一页就行评论。
iOS的fixed
解决思路:
- Big-man这里提出一种思考的思路,具体实现如何,Big-man就不敢保证了。思路如下:
- 既然在
iOS
下由于软键盘触发后, 页面的fixed
元素会失效, 导致跟随页面一起滚动, 那么假如一页页面不会过长出现滚动的效果,那么即便fixed
元素失效, 也无法跟随页面滚动, 也就不会出现上面的情况了。 - 那么按照这个思路, 如果使
fixed
元素的父级不出现滚动, 而将原来body
滚动的区域移动到main
内部, 而header
和footer
的样式不变。代码修改如下:
<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;
}
- 另外,这里的
header
和footer
使用的是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 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
的时候, 设置onerror
为null
。
<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
属性, 例如calc
等width:90%;width: calc(ssadft)
; - 滚动事件不会处触发
touchmove
事件;
手机浏览器通用问题:
- 弹出层
touchmove
滚动, 会触发touch
滚动(出现前提是body
中有滚动轴)。 - 手机网站表层div滑动,导致底层body滑动(
touchmove
的阻止)。 body
很长,可以滑动,body
头部有一个模拟下拉的选择框。下拉选择有滚动轴,如下图:
- Big-man给body一个overflow:hidden和高度是没有用的。手机网站上背景还是可以滑动,然后Big-man给body一个
touchmove
的preventdefault()
阻止事件,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