mishe / blog Goto Github PK
View Code? Open in Web Editor NEW前端碰上的问题或体会
前端碰上的问题或体会
移动端Web分级标准,是根据现有移动设备的使用情况的比例进行梳理而得到了分级标准,为现在Web开发以及HybridApp开发提供了必要参考。
在做移动开发的时候,可根据此分级进行相关测试
一级:用户使用的最多的设备、平台和浏览器,占比>90%,作为大多数的移动web都需要能在此上面完成所有应有功能
二级:有一定用户群,占比在5—10%左右的,作为一般类web应用需要兼容此级别
三级:占比<2%的用户群,此类设备、平台等都比较陈旧,但是还是有一部分用户在使用
作为一级的类目里面的设备,系统和浏览器是自测的必要选项
一级以下的类目不作为必须的自测项,手上有设备方便看的话,可以测试一下
function ksort(arr){
var a=arr.slice(),len=a.length,start=0,end=len-1;
function sort(start,end){
if(start>end)
return;
var tmp=a[start],i=start,j=end,t;
while(i!=j){
while(a[j]>=tmp && i<j){
j--
}
while(a[i]<=tmp && i<j){
i++
}
if(i<j){
t=a[i];
a[i]=a[j];
a[j]=t;
}
}
a[start]=a[i];
a[i]=tmp;
sort(start,i-1)
sort(i+1,end)
}
sort(start,end)
return a;
}
d=[1,4,3,9,0,2,5,7,8,21,44,43];
ksort(d)
var setPos=function(o,start,end){
if(start===undefined) start=o.value.length;
if(end===undefined) end=o.value.length;
if(o.setSelectionRange){//W3C
setTimeout(function(){
o.setSelectionRange(start,end);
o.focus();
},0);
}else if(o.createTextRange){//IE
var textRange=o.createTextRange();
textRange.moveStart("character",start);
textRange.moveEnd("character",end);
textRange.select();
}
};
setPos(dom) //改变光标到文本的最后
setPos(dom,0,0) //改变光标到文本的开始
setPos(dom,1,5) //选择第2-5的4个字符
具体代码参见 https://github.com/mishe/store
本地实际应用建议绑定当前的用户,解决不能用户看到不同的缓存,可以做下适当的包装,比如:
setCache:function(key,value,exp){
//过期时间默认1天
exp=exp||86400;
store.set(key+'_'+curUserID, value,exp);
},
getCache:function(key){
return store.get(key+'_'+curUserID);
}
另外还又可以尝试结合一些简单的加解密技术,来保证客户端缓存数据的相对安全,但势必会造成性能上的损失。
//简单的缓存
store.set('a',{a:1,b:2})
//增加过期时间:单位是秒
store.set('a',{a:1,b:2},3600)
//清除指定缓存
store.set('a',null)
//或者
sotre.set('a');//为了代码的可读性,不建议这样使用
store.get('a')
//log:{a:1,b:2}
store.del('a');
store.clearExp();
store.clearAll()
//和set的区别是,这个会检测是否已经存在同名的缓存,如果存在,就不做任何处理
// 单set是会覆盖当前值的
store.add('b',{c:1,d:1},3600)
//过期时间由原来的1小时,变成了2小时,并且是从创建时间计算的
store.setExp('a',7200)
store.getExp('a')
ios下touchstart和touchemove返回的event是同一个对象,
而在android下不是同一个。
$().offset()
获得元素在网页中的坐标,会计算模盒距离(padding,border,margin)
$().position()
获得元素相对于父元素()的坐标,不计算模盒距离
el.getBoundingClientRect()
获得元素相对于浏览器的可见区域左上角的距离,会计算模盒距离(padding,border,margin)
居中一直是CSS中被抱怨的典型。为什么实现起来这么辛苦?所以有人被嘲笑。我觉得问题不是没有办法做到,只是视情况而定,有很多不同方式,但是很难弄清楚应该用何种方式。
内联元素(inline or inline-*)居中?
你可以让他相对父级块级元素居中对齐
.center-children {
text-align: center;
}
你可以通过设置margin-left和margin-right为auto让它居中(同时还要设置width,否则它就会承满整个容器,无法看出居中效果),如。
.center-me {
margin: 0 auto;
}
如果你有很匀块级元素需要水平居中成一行,你最好使用一个不同的display类型。这是一个使用inline-block和flex的例子。
在线示例: http://jsfiddle.net/ourjs/0b6b7wt8/
<main class="inline-block-center">
<div>
I'm an element that is block-like with my siblings and we're centered in a row.
</div>
<div>
I'm an element that is block-like with my siblings and we're centered in a row. I have more content in me than my siblings do.
</div>
<div>
I'm an element that is block-like with my siblings and we're centered in a row.
</div>
</main>
<main class="flex-center">
<div>
I'm an element that is block-like with my siblings and we're centered in a row.
</div>
<div>
I'm an element that is block-like with my siblings and we're centered in a row. I have more content in me than my siblings do.
</div>
<div>
I'm an element that is block-like with my siblings and we're centered in a row.
</div>
</main>
body {
background: #f06d06;
font-size: 80%;
}
main {
background: white;
margin: 20px 0;
padding: 10px;
}
main div {
background: black;
color: white;
padding: 15px;
max-width: 125px;
margin: 5px;
}
.inline-block-center {
text-align: center;
}
.inline-block-center div {
display: inline-block;
text-align: left;
}
.flex-center {
display: flex;
justify-content: center;
}
垂直居中在CSS中有点棘手
内联元素(inline or inline-*)居中,像文本和链接那样的?
有时侯元素可以表现像垂直居中,只是因为它们有相等的上下padding
.link {
padding-top: 30px;
padding-bottom: 30px;
}
如果padding因为某些原因不能用,而且文本不会换行的情况下,你可以使用line-height,让其与height相等去对齐文本。
.center-text-trick {
height: 100px;
line-height: 100px;
white-space: nowrap;
}
上下等padding的方式也可以让多行居中,但是如果这方法没用,你可以让这些文字的容器按table cell模式显示,然后设置文字的vertical-align属性对齐,就像talbe那样
在线演示: http://jsfiddle.net/ourjs/0fn2u4rc/
<table>
<tr>
<td>
I'm vertically centered multiple lines of text in a real table cell.
</td>
</tr>
</table>
<div class="center-table">
<p>I'm vertically centered multiple lines of text in a CSS-created table layout.</p>
</div>
body {
background: #f06d06;
font-size: 80%;
}
table {
background: white;
width: 240px;
border-collapse: separate;
margin: 20px;
height: 250px;
}
table td {
background: black;
color: white;
padding: 20px;
border: 10px solid white;
/* default is vertical-align: middle; */
}
.center-table {
display: table;
height: 250px;
background: white;
width: 240px;
margin: 20px;
}
.center-table p {
display: table-cell;
margin: 0;
background: black;
color: white;
padding: 20px;
border: 10px solid white;
vertical-align: middle;
}
出于很多方面的原因,不知道网页布局的高度是相当普遍的。
但是如果你的布局有一个固定高度,你就可以这样垂直居中:
.parent {
position: relative;
}
.child {
position: absolute;
top: 50%;
height: 100px;
margin-top: -50px; /* 如果没有使用: border-box; 的盒子模型则需要设置这个 */
}
尽管未知,但你仍有可能向上推移50%的宽度
在线演示: http://jsfiddle.net/ourjs/9sLf7p56/
.parent {
position: relative;
}
.child {
position: absolute;
top: 50%;
transform: translateY(-50%);
}
这并不奇怪,使用flexbox会容易非常多
<main>
<div>
I'm a block-level element with an unknown height, centered vertically within my parent.
</div>
</main>
body {
background: #f06d06;
font-size: 80%;
}
main {
background: white;
height: 300px;
width: 200px;
padding: 20px;
margin: 20px;
display: flex;
flex-direction: column;
justify-content: center;
resize: vertical;
overflow: auto;
}
main div {
background: black;
color: white;
padding: 20px;
resize: vertical;
overflow: auto;
}
如果元素的宽度和高度是固定的,你需要先绝对居中,然后上移和左移50%的宽度即可,这种方案有极好的跨浏览器支持。
.parent {
position: relative;
}
.child {
width: 300px;
height: 100px;
padding: 20px;
position: absolute;
top: 50%;
left: 50%;
margin: -70px 0 0 -170px;
}
如果你不知道高度和宽度(可变的),你可以使用transofrm属性在两个方向都平移负50%
.parent {
position: relative;
}
.child {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
touchstart //当手指接触屏幕时触发
touchmove //当已经接触屏幕的手指开始移动后触发
touchend //当手指离开屏幕时触发
touchcancel
gesturestart //当两个手指接触屏幕时触发
gesturechange //当两个手指接触屏幕后开始移动时触发
gestureend
onorientationchange
orientationchange
touches
targetTouches
changedTouches
clientX // X coordinate of touch relative to the viewport (excludes scroll offset)
clientY // Y coordinate of touch relative to the viewport (excludes scroll offset)
screenX // Relative to the screen
screenY // Relative to the screen
pageX // Relative to the full page (includes scrolling)
pageY // Relative to the full page (includes scrolling)
target // Node the touch event originated from
identifier // An identifying number, unique to each touch event
屏幕旋转事件:onorientationchange
function orientationChange() {
switch(window.orientation) {
case 0:
alert("肖像模式 0,screen-width: " + screen.width + "; screen-height:" + screen.height);
break;
case -90:
alert("左旋 -90,screen-width: " + screen.width + "; screen-height:" + screen.height);
break;
case 90:
alert("右旋 90,screen-width: " + screen.width + "; screen-height:" + screen.height);
break;
case 180:
alert("风景模式 180,screen-width: " + screen.width + "; screen-height:" + screen.height);
break;
};};
addEventListener('load', function(){
orientationChange();
window.onorientationchange = orientationChange;
});
// 双手指滑动事件
addEventListener('load', function(){ window.onmousewheel = twoFingerScroll;},
false // 兼容各浏览器,表示在冒泡阶段调用事件处理程序 (true 捕获阶段)
);
function twoFingerScroll(ev) {
var delta =ev.wheelDelta/120; //对 delta 值进行判断(比如正负) ,而后执行相应操作
return true;
};
支持移动端上传,基于jquery,使用范例:
$('.add_photo_btn').Mobile_upload({
multiple:true, //多图上传
ajax:{
url:st.cfg.upload, //ajax上传的接口地址
loading:this.$el.find('.loading') //相关的loading显示
},
max:9, //最大单次选中文件
maxFileSize:9*1024*1024, //最大文件尺寸
callback:function(result,name,postName){ //上传成功后的回调
}
});
ios上比较突出;特别是内容还包含input等输入块的时候
可以采用转变pos:abs的方式来解决的问题,然后采用定时器来修正弹出层的定位。
if($.isIOS()){
this.pop=true;
var top=document.body.scrollTop;
window.scroll(0,top);
self.$el.find('.reply_wrap').css({'position':'absolute','top':document.body.scrollTop,'height':screen.availHeight+200});
var interval,count=0;
interval=setInterval(function(){
count+=16;
if(count>1000){
clearInterval(interval);
}else{
window.scroll(0,top);
}
},16);
}
window.g = function(id){return document.getElementById(id)};
function doit(id) {
doit_cancel(id);
var style = document.createElement('style');
var head = document.documentElement;
head.appendChild(style);
style.type = 'text/css';
style.id=id
if (style.styleSheet){
style.styleSheet.cssText = 'button {background-color: #ff6699;}';
}
else {
style.appendChild(document.createTextNode('button {background-color: #ff6699;}'));
}
}
function doit_cancel(id) {
var sheets,index=0, c,result;
sheets = document.getElementsByTagName('style');
while (index < sheets.length) {
c = sheets[index];
if (c.id === id) {
result = c;
break;
}
index++;
}
if (result) {
c = result.parentNode;
c.removeChild(result);
}
}<div id="out"></div>
<button type="button" onclick="doit('aa')">doit</button> ddddd
<button type="button" onclick="doit_cancel('aa')">cancel</button>
<style>
button {border: 2px solid #f00;}
</style>
<div id="out"></div>
<button type="button" onclick="doit('aa')">doit</button> ddddd
<button type="button" onclick="doit_cancel('aa')">cancel</button>
<style>
button {border: 2px solid #f00;}
</style>
或者采用以下方法:
function doit(id) {
doit_cancel(id);
var elem = document.createElement('div');
elem.innerHTML = '<br /><style type="text/css" id="'+id+'">button {border: 2px solid blue;}</style>';
var body = document.getElementsByTagName('body')[0];
body.appendChild(elem);
//IE7 以下有解析BUG
if(!!window.ActiveXObject&&document.documentMode<=7){
var sheets=document.styleSheets,len=sheets.length,index= 0,temp;
while(index<len){
if (document.styleSheets[index].id === id) {
for(;index<len-1;index++) {
temp=document.styleSheets[index].cssText;
document.styleSheets[index].cssText=document.styleSheets[index+1].cssText;
document.styleSheets[index+1].cssText=temp;
}
break;
}
index++;
}
}
}
function doit_cancel(id) {
var sheets,index=0, c,result;
sheets = document.getElementsByTagName('style');
while (index < sheets.length) {
c = sheets[index];
if (c.id === id) {
result = c;
break;
}
index++;
}
if (result) {
c = result.parentNode;
c.parentNode.removeChild(c);
}
}
<meta name="viewport" content="target-densitydpi=device-dpi,width=640,user-scalable=no" />
修正方案
<meta name="viewport" content="width=640,initial-scale=0.5,maximum-scale=0.5,minimum-scale=0.5,user-scalable=no">
简便易行,适用情况:
网页设计仅针对手机屏幕,并且没有需要根据手机屏幕尺寸进行UI调整的内容,既没有媒体查询的CSS
产品层面可以不考虑非主流设备或浏览器的兼容性,例如黑莓的某些设备或搜狗浏览器这类的不支持640定宽渲染的浏览器
当手机横竖切换时,能够容忍部分手机在横屏浏览网页时,可能出现的问题(潜在问题)。
这个方案是强制让手机浏览器按640的比例来呈现网页,让浏览器去做尺寸适配;这个方案我觉得其实非常好,绝大多数的场景都可以支持,而且开发起来简单高效,不需要考虑各种尺寸和样式表的单位换算问题。
这个方案有点类似css zoom,就是浏览器将整个网页进行了缩放,注意一点:PC端的使用浏览器的开发工具进行缩放的时候,譬如使用iphone 5s 模式预览,可能会有问题,比如border:1px的时候可能会不可见,字体会有些模糊等,但是手机端展现的时候不会出现这些问题,因为绝大多数手机目前屏幕分辨率都足够高,而且专门针对viewport做了处理,所以这个问题可以忽略。
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
上面这个不用解释了,绝大多数知名网站都这么做的,但是为了能够让图片和字体去做自适应,通常的做法是使用rem来作为单位,然后再启用javascript判定屏幕宽度,动态改变html元素的fontsize。
为了方便计算,通常会默认将html的font-size设置为100px,这样方便计算,例如你的网页字体是16px,只需要写成0.16rem就行了,然后假如设计稿是按640宽度设计,切图的时候完全按设计稿来进行单位换算,然后在js中获取屏幕宽度,根据比例:100/x = 640/documentWidth 计算html的字体大小即可,当然要加入一个window.resize事件重新计算,以防横竖屏切换。
第二种方案有几个弊端:
由于背景图片无法做缩放,(background-size:cover|contain)只能针对单张图片,而对于sprite css image无力解决,所以这里要求如果有背景图,全部制作成单张的,然后再加上background-size:cover|contain,这里凸显出使用css font的好处了...
页面中的所有单位都需要用rem来计算,虽然 X/100是很简单的计算,但是书写的时候还是让人厌烦。
如果有图片必须指定宽度,不然图片会按原始宽度进行渲染,当屏幕尺寸与设计预期尺寸不一致时,就会出错了。
根据上面的描述,我做了两个工具:
制作时依然采用px作为单位,通过watch插件监听css文件的改变,然后动态转换px2rem。
根据图片生成auto convert css icon文件,类似grunt-sprite,但只生成对应的css,而不合成图片,这样对于将以往的css sprite工具生成的图片可以低成本切换成单一图片ICON
开发思路是这样:
开发时区分目录,例如src表示源码,dest目录标示转换后的静态文件,目前上规模的工程都这么做,应该没什么成本了,然后css依然使用像素作为单位。
通过grunt或gulp插件来监听文件改变,然后做copy,sync之类的处理,同时这里将css中的px按设定的比率转换成rem。
不能在网页中直接嵌入内联style,或者需要自行转换单位之后嵌入。
监听脚本:
(注:这里假定开发的时候依据的宽度的640,默认字体是32像素)
// 根据屏幕宽度适配字体大小
$(window).on('resize', function () {
var width = document.documentElement.clientWidth;
$('html').css('font-size', (width / 640 * 32) + 'px');
}).triggerHandler('resize');
第一个定宽开发的方案其实非常适合小团队做推广页面,因为这样避免了各种工具的整合,降低了开发复杂度。
第二个方案适合需要适应绝大多数尺寸浏览器的项目,同时如果配置好打包工具,可以很大程度降低难度,将开发和部署解耦,开发代码比较清晰。
分页模板(underscore)
<div class="pages">
<% if(countPage<5){
for(var i=1;i<=countPage;i++){ %>
<a href="javascript:;" data-id="<%=i%>" class="<%=curPage==i?'curPage':''%>"><%=i%></a>
<% }
}else{
if(curPage<5){
for (var i = 1; i <5; i++) { %>
<a href="javascript:;" data-id="<%=i%>" class="<%=curPage==i?'curPage':''%>"><%=i%></a>
<% } %>
...<a href="javascript:;" data-id="<%=countPage%>"><%=countPage%></a>
<% }else if(curPage>4 && curPage<countPage-3){ %>
<a href="javascript:;" data-id="1">1</a>...
<% for (var i = curPage-2; i <= curPage+2; i++) { %>
<a href="javascript:;" data-id="<%=i%>" class="<%=curPage==i?'curPage':''%>"><%=i%></a>
<% } %>
...<a href="javascript:;" data-id="<%=countPage%>"><%=countPage%></a>
<% }else{ %>
<a href="javascript:;" data-id="1">1</a>...
<% for (var i = countPage-4; i <= countPage; i++) { %>
<a href="javascript:;" data-id="<%=i%>" class="<%=curPage==i?'curPage':''%>"><%=i%></a>
<% }
}
}
if(pageSize>0 && countPage>0){ %>
每页显示 <select>
<option value="10"<%=pageSize==10?' selected':''%>>10</option>
<option value="20"<%=pageSize==20?' selected':''%>>20</option>
<option value="40"<%=pageSize==40?' selected':''%>>40</option>
<option value="100"<%=pageSize==100?' selected':''%>>100</option>
</select>
跳转到<input type="text" value="<%=curPage%>">
<% }%>
</div>
需要传入参数:
pages.countPage=Math.floor(len/pageSize)+(len%pageSize>0?1:0);
pages.curPage=parseInt(page);
pages.pageSize=0;
$('#pages').html(_.template(pageTemplate,pages));
events:{
'click .pages a':'pages',
'change .pages select':'changePageSize',
'blur .pages input':'gotoInputPage',
},
pages:function(event){
var self=$(event.currentTarget);
this.render(self.attr('data-id'));
},
changePageSize:function(event){
var self=$(event.currentTarget);
this.pageSize=self.val();
this.render();
},
gotoInputPage:function(event){
var self=$(event.currentTarget);
this.render(self.val());
}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>放大镜</title>
<style type="text/css">
/* basic */
body { background:#f9f9f9; margin:0 auto; font:12px/24px tahoma, Helvetica, Arial; text-align:center; }
body,iframe,div,p,h1,h2,h3,h4,h5,h6,fieldset,ul,dl,dt,dd,form,input,button,textarea,select,i { margin:0px; padding:0px; font-weight:normal; }
img {border:none; }
.main{ font:12px/24px "Microsoft YaHei"; width:960px; margin:100px auto; position:relative; text-align:left; }
.main:after{ content:""; clear:both; display:table; }
#zoomPic{ width:400px; height:400px; position:relative; left:0; }
</style>
</head>
<body>
<div class="main">
<div id="zoomPic" bigSrc="http://p1.vanclimg.com/product/0/1/8/0180997/big/0180997-1j201208211605141213.jpg">
<img src="http://p1.vanclimg.com/product/0/1/8/0180997/mid/0180997-1j201208211605141213.jpg" id="show_pic" />
</div>
</div>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script type="text/javascript">
$.fn.extend({
simpleZoom:function(options){
var opt= {
markSize : [200, 100], //放大镜宽高
zoomSize : [400, 400], //需要放大的区域宽高
zoomImg : [800, 800] //需要放大的区域的图片的宽高
};
opt = $.extend(opt, options);
return this.each(function(){
var markBox = $(this),
bigSrc=markBox.attr('bigSrc'),
markBoxSize = [markBox.width(), markBox.height(), markBox.offset().left, markBox.offset().top],
mask = $("<i></i>"),
markCss = {position:"absolute", top:0, left:0, width:opt.markSize[0], height:opt.markSize[1], backgroundColor:"#000", opacity:.5, filter:"alpha(opacity=50)", display:"none", cursor:"crosshair"},
zoomCss = {position:"absolute", top:0, left:markBoxSize[0], width:opt.zoomSize[0], height:opt.zoomSize[1],display:"none",overflow:"hidden"},
zoomBox =$('<div><img src="'+bigSrc+'" style="position:absolute"></div>'),
show_w = markBoxSize[0]-opt.markSize[0],
show_h = markBoxSize[1]-opt.markSize[1];
// new Image(bigSrc);//预加载大图;
markBox.append(mask);
mask.css(markCss);
zoomBox.css(zoomCss)
markBox.append(zoomBox);
markBox.mouseover(function(){
mask.show();
zoomBox.show();
}).mouseout(function(){
mask.hide();
zoomBox.hide();
}).mousemove(function(e){
var l = e.pageX-markBoxSize[2]-(opt.markSize[0]/2),
t = e.pageY-markBoxSize[3]-(opt.markSize[1]/2);
l=l<0?0:(l>show_w?show_w:l)
t=t<0?0:(t>show_h?show_h:t)
mask.css({left:l, top:t});
var z_x = -(l/show_w)*(opt.zoomImg[0]-opt.zoomSize[0]),
z_y = -(t/show_h)*(opt.zoomImg[1]-opt.zoomSize[1]);
zoomBox.find("img").css({left:z_x, top:z_y});
});
});
}
})
$(function(){
$("#zoomPic").simpleZoom({
markSize : [100, 100],
zoomSize : [400, 400],
zoomImg : [800, 800]
});
});
(function($){
})(jQuery);
</script>
</body>
</html>
这几天,第三轮全站优化结束,测试项目在2G首屏载入速度取得了一些优化成绩,对比下来有10s左右的差距:
这次优化工作结束后,已经是第三次大规模折腾公司框架了,这里将一些自己知道的移动端的建议提出来分享下,希望对各位有用
文中有误请您提出,以免误人自误
单页or多页
spa(single page application)也就是我们常常说的web应用程序webapp,被认为是业内的发展趋势,主要有两个优点:
① 用户体验好
② 可以更好的降低服务器压力
但是单页有几个致命的缺点:
① SEO支持不好,往往需要单独写程序处理SEO问题
② webapp本身的内存管理难,Javascript、Css非常容易互相影响
当然,这里不是说多页便不能有好的用户体验,不能降低服务器压力;多页也会有变量污染的问题发生,但造成webapp依旧是“发展趋势”,而没有大规模应用的主要原因是:
其实webapp的最大问题与上述几点没有关系,实际上阻碍webapp的是技术门槛与手机性能,硬件方面不必多说,这里主要说技术门槛。
webapp做的好,可以玩动画,可以玩真正意义上的预加载,可以玩无缝页面切换,从某些方面甚至可以媲美原生APP,这也是webapp受到追捧的原因。
但是,以上很容易被玩坏!因为webapp模式不可避免的需要用到框架,站点需要一个切实可行的控制器来管理History以及页面view实例化工作,于是大家会选择诸如:
Backbone、angularJS、canJs之类的MVC框架,于是整个前端的技术要求被平白无故的提升了一个等级,原来操作dom可以做的事情,现在不一定能做了。
很多人对以上框架只停留在使用层面,几轮培训后,对底层往往感到一头雾水,就算开发了几个项目后,仍然还是只能了解View层面的东西;有对技术感兴趣的同事会慢慢了解底层,但多数仍然只关注业务开发,这个时候网站体验便会受到影响,还让webapp受到质疑。
① 精英团队在公司有钱并且网站周期在两年以上的话可以选用webapp模式
② 一般团队还是使用多页吧,坑不了
③ 更好的建议是参考下改变后的新浪微博,采用伪单页模式,将网站分为几个模块做到组件化开发,碰到差距较大的页面便刷新也无不可
PS:事实上webapp模式的网站体验确实会好一点
移动前端依旧离不开框架,而且框架呈变化状态,以我厂为例,我们几轮框架选型是:
① 多页应用+jQuery
② jQuery mobile(这个坑谁用谁知道)
③ 开始webapp模式(jQuery+requireJS+Backbone+underscore)
④ 瘦身(zepto+requireJS+Backbone View部分+underscore)
……
移动大潮来临后,浏览器基本的兼容得到了保证,所以完整的jQuery变得不是那么必须,因为尺寸原因,所以一般被zepto替换,zepto与jQuery有什么差异呢?
首先,Zepto与jQuery的API大体相似,但是实现细节上差异甚大,我们使用Zepto一般完成两个操作:
① dom操作
② ajax处理
但是我们知道HTML5提供了一个document.querySelectorAll的接口,可以解决我们90%的需求,于是jQuery的sizzle便意义不大了,后来jQuery也做了一轮优化,让用户打包时候选择,需要sizzle才用。
其次jQuery的一些属性操作上做足了兼容,比如:
el.css('transform', 'translate(-968px, 0px) translateZ(0px)')
//jQuery会自动根据不同浏览器内核为你处理为:
el.css('-webkit-transform', 'translate(-968px, 0px) translateZ(0px)')
又比如说,以下差异比比皆是:
el.hide(1000);//jQuery具有动画,Zepto不会鸟你
然后,jQuery最初实现animate是采用js循环设置状态记录的方式,所以可以有效的记住状态暂停动画元素;Zepto的animate完全依赖于css3动画,暂停需要再想办法
其实,我们简单从实现上就可以看出,Zepto这里是偷懒了,其实现最初就没有想考虑IE,所以winphone根本不能愉快的玩耍
zepto.Z = function(dom, selector) {
dom = dom || []
dom.__proto__ = $.fn
dom.selector = selector || ''
return dom
}
真实的差异还有很多,我这里也没法一一列出,这里要说明的一个问题其实就是:
jQuery大而全,兼容、性能良好;Zepto针对移动端定制,一些地方缺少兼容,但是尺寸小
zepto设计的目的是提供jquery的类似的APIs,不以100%覆盖jquery为目的,一个5-10k的通用库、下载并执行快、有一个熟悉通用的API,所以你能把你主要的精力放到应用开发上。
上图是1.8版本与Zepto完整版的对比,Gzip在2G情况下20K造成的差距在2-5s之间,3G情况会有1s的差距,这也是我们选择Zepto的原因。
zepto提供简单手势库,这个大坑,谁用谁知道!!!几个有问题的地方:
① 事件直接绑定至document,性能浪费
② touchend时候使用settimeOut导致event参数无效,所以preventDefault无效,点透等情况也会发生
① selector
如上所述,Zepto的选择器只是jQuery的一个子集,但是这个子集满足我们90%的使用场景
② clone
Zepto的clone不支持事件clone,这句话的意思是dom clone后需要自己再处理事件,举个例子来说:
var el = $('.el');
el.on('click', function() {
alert(1)
})
//true的情况jQuery会连带dom事件拷贝,Zepto没有做这个处理
//jQuery库,点击clone的节点会打印1,Zepto不会
var el1 = el.clone(true);
$('#wrap').append(el1);
这个差异还比较好处理,现在都会使用事件代理,所以没clone事件也在没问题的……
这里简单看看细节实现:
clone: function (elem, dataAndEvents, deepDataAndEvents) {
var i, l, srcElements, destElements,
clone = elem.cloneNode(true),
inPage = jQuery.contains(elem.ownerDocument, elem);
// Fix IE cloning issues
if (!support.noCloneChecked && (elem.nodeType === 1 || elem.nodeType === 11) &&
!jQuery.isXMLDoc(elem)) {
// We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2
destElements = getAll(clone);
srcElements = getAll(elem);
for (i = 0, l = srcElements.length; i < l; i++) {
fixInput(srcElements[i], destElements[i]);
}
}
// Copy the events from the original to the clone
if (dataAndEvents) {
if (deepDataAndEvents) {
srcElements = srcElements || getAll(elem);
destElements = destElements || getAll(clone);
for (i = 0, l = srcElements.length; i < l; i++) {
cloneCopyEvent(srcElements[i], destElements[i]);
}
} else {
cloneCopyEvent(elem, clone);
}
}
// Preserve script evaluation history
destElements = getAll(clone, "script");
if (destElements.length > 0) {
setGlobalEval(destElements, !inPage && getAll(elem, "script"));
}
// Return the cloned set
return clone;
},
function cloneCopyEvent(src, dest) {
var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events;
if (dest.nodeType !== 1) {
return;
}
// 1. Copy private data: events, handlers, etc.
if (dataPriv.hasData(src)) {
pdataOld = dataPriv.access(src);
pdataCur = dataPriv.set(dest, pdataOld);
events = pdataOld.events;
if (events) {
delete pdataCur.handle;
pdataCur.events = {};
for (type in events) {
for (i = 0, l = events[type].length; i < l; i++) {
jQuery.event.add(dest, type, events[type][i]);
}
}
}
}
// 2. Copy user data
if (dataUser.hasData(src)) {
udataOld = dataUser.access(src);
udataCur = jQuery.extend({}, udataOld);
dataUser.set(dest, udataCur);
}
}
clone: function(){
return this.map(function(){ return this.cloneNode(true) })
},
下面是Zepto的clone实现,我啥也不说了,为什么jQuery这么大呢,是有道理的。
③ data
Zepto的data只能存储字符串,你想存储复杂对象的话便把他先转换为字符串
④ offset
el.offset()
//Zepto返回
Object {left: 8, top: 8, width: 485, height: 18}
//jQuery返回
Object {top: 8, left: 8}
getBoundingClientRect 函数是W3C组织在第一版本的W3C CSSOM View specification草案中确定的一个标准方法,在此之前,只有IE浏览器是支持该方法的,W3C在这次草案中把它扶正成为标准。
getBoundingClientRect 方法返回的是调用该方法的元素的TextRectangle对象,该对象具有top、left、right、bottom四个属性,分别代表该元素上、左、右、下四条边界相对于浏览器窗口左上角(注意,不是文档区域的左上角)的偏移像素值。
offset: function(coordinates){
if (coordinates) return this.each(function(index){
var $this = $(this),
coords = funcArg(this, coordinates, index, $this.offset()),
parentOffset = $this.offsetParent().offset(),
props = {
top: coords.top - parentOffset.top,
left: coords.left - parentOffset.left
}
if ($this.css('position') == 'static') props['position'] = 'relative'
$this.css(props)
})
if (this.length==0) return null
var obj = this[0].getBoundingClientRect()
return {
left: obj.left + window.pageXOffset,
top: obj.top + window.pageYOffset,
width: Math.round(obj.width),
height: Math.round(obj.height)
}
},
jQuery offsetoffset: function (options) {
if (arguments.length) {
return options === undefined ?
this :
this.each(function (i) {
jQuery.offset.setOffset(this, options, i);
});
}
var docElem, win,
elem = this[0],
box = { top: 0, left: 0 },
doc = elem && elem.ownerDocument;
if (!doc) {
return;
}
docElem = doc.documentElement;
// Make sure it's not a disconnected DOM node
if (!jQuery.contains(docElem, elem)) {
return box;
}
// Support: BlackBerry 5, iOS 3 (original iPhone)
// If we don't have gBCR, just use 0,0 rather than error
if (typeof elem.getBoundingClientRect !== strundefined) {
box = elem.getBoundingClientRect();
}
win = getWindow(doc);
return {
top: box.top + win.pageYOffset - docElem.clientTop,
left: box.left + win.pageXOffset - docElem.clientLeft
};
},
差距不大,jQuery的更加严谨,总会做很多兼容,jQuery大是有道理的
MVC框架选择
MVC框架流行的有Backbone、angularJS、reactJS、canJS等,我个人比较熟悉Backbone与canJS,近期也在整理canJS的一些笔记
首先提一下Backbone,我认为其最优秀的就是其View一块的实现,Backbone的View规范化了dom事件的使用,避免了事件滥用,避免了事件“失效”
但是Backbone的路由处理一块很弱,事实上一点用也没有,而且就算view一块的继承关系也非常难以处理,extend实现是:
var extend = function (protoProps, staticProps) {
var parent = this;
var child;
// The constructor function for the new subclass is either defined by you
// (the "constructor" property in your `extend` definition), or defaulted
// by us to simply call the parent's constructor.
if (protoProps && _.has(protoProps, 'constructor')) {
child = protoProps.constructor;
} else {
child = function () { return parent.apply(this, arguments); };
}
// Add static properties to the constructor function, if supplied.
_.extend(child, parent, staticProps);
// Set the prototype chain to inherit from `parent`, without calling
// `parent`'s constructor function.
var Surrogate = function () { this.constructor = child; };
Surrogate.prototype = parent.prototype;
child.prototype = new Surrogate;
// Add prototype properties (instance properties) to the subclass,
// if supplied.
if (protoProps) _.extend(child.prototype, protoProps);
// Set a convenience property in case the parent's prototype is needed
// later.
child.__super__ = parent.prototype;
return child;
};
JavaScript
child.__super__ = parent.prototype;
这是一段极为糟糕的设计,他是将parent原型的指向给到了类的的属性上,这里可以看做静态方法,那么我在实际使用的时候要如何使用呢?
我在内部原型链上或者实例方法一般使用this便能指向本身,但是却不能执行本类的方法,如果要使用指向构造函数我需要这么做:
this.constructor
this.constructor.__super__
如果我这里想要执行父类的一个方法,还得关注起作用域指向,于是只能这样写
this.constructor.__super__.apply(this, arguments)
而我总是认为javascript的construct未必非常靠谱,于是整个人都不好了,所以在一轮使用后,基本便放弃Backbone了,但是Backbone优秀的一面也不能抹杀,我们可以借鉴Backbone实现一些更加适合项目的基础架子
Backbone另一个令人诟病的地方是其插件少,其实这里有点苛刻,移动端才兴起不久,webapp的项目又少,这里没有是很正常,别人的插件也未必能用的顺心。
angularJs我本身没有实际使用过,不好评价,根据一些朋友的实际使用情况可以得出一个结论:
规定的非常死,业务代码可保持一致,入门简单深入难,一旦出现问题,不太好改,对技术要求较高
这里各位根据实际情况选择就好,我这里的建议还是自己读懂一个MV*的框架,抽取需要的重写,像angularJS一次升级,之前的项目如何跟着升级,这些问题很头疼也很实际。
上次抱着解决webappSEO难题时候对reactJS有所接触,其源码洋洋洒洒10000行,没有一定功力与时间还是暂时不碰为好。
canJS学习成本与Backbone差不多,我这边准备出系列学习笔记,好不好后面调研再说。
总结一句:不建议直接将业务库框架直接取来使用,更不建议使用过重的业务框架,最好是能明白框架想要解决的问题,与自己项目的实际需求,自己造轮子知根知底。
最好给出一个小小的建议,希望对各位有用:
requireJS+Zepto+阉割版underscore(将其中不太用到的方法去掉,主要使用模板引擎一块)+ Fastclick
建议自己写,不要太臃肿,可以抄袭,可以借鉴,不要完全拿来就用
这样出来的一套框架比较轻量级,知根知底,不会出现改不动的情况,最后提一句:不经过调研,没有实际场景在框架中玩模式,玩高级理念死得快,不要为技术而技术。
兵无定势,水无常形,按照之前所说,我们选取了对我们最优的框架,做出来的网站应该很快,但第一轮需求结束后有第二轮,第二轮需求结束后有第三轮,网站版本会从1.1-X.1,业务的增长以及市场份额的角力带来的是一月一发布,一季一轮替,没有不变的道理。
框架最大的敌人是需求,代码最大的敌人是变更,最开始使用的是自己熟悉的技术,突然一天多出了一些莫名其妙的场景:
① webapp模式很不错,为了快速业务发展,将接入Hybrid技术,并且使用一套代码
② 微信入口已经很火了,为了快速业务发展,将接入微信入口,并且使用一套代码
③ UI组件已经旧了,换一批ios8风格的组件吧
④ 全站样式感觉跟不上潮流了,换一套吧
网站变慢的核心原因是尺寸的膨胀,尺寸优化才是前端优化的最重要命题,①、②场景是不可预知场景,面对这种不可预知场景,会写很多桥接的代码,而这类代码往往最后都会证明是不好的!
框架首次处理未知场景所做的代码,往往不是最优的,如Hybrid、如微信入口
剩下两个场景是可预见的改变,但是此类变更会带来另一个令人头疼的问题,新老版本交替。业务20多个业务团队,不可能一个版本便全部改变,便有个逐步推进的过程。
全站样式替换/对未知场景的代码优化,很多时候为了做到透明,会产生冗余代码,为了做兼容,常常有很长一段时间新老代码共存的现象
于是不可预知造成的尺寸膨胀,经过重构优化,而为了做兼容,居然会造成尺寸进一步的增加
所谓优化不一定马上便有效果,开发人员是否扛得住这种压力,是否有全团队推动的能力会变得比本身技术能力更加重要
事实上的情况复杂的多,以上只是一厢情愿的以“接口统一”、“透明升级”为前提,但是透明的代价是要在重构代码中做兼容,而兼容又本身是需要重构掉的东西,当兼容产生的代码比优化还多的时候,我们可能就会放弃兼容,而提供一套接口完全不统一的东西;更加真实情况是我们根本不会去做这种对比,便直接将老接口废掉,这个时候造成的影响是“天怒人怨”,但是我们爽了,爽了的代价是单个团队的推动安抚。
这里请参考angularJS升级,新浪微博2.0接口与1.1不兼容问题,这里的微信接口提出,难保一年后不会完全推翻……
所以,尺寸变大的主要原因是因为冗余代码的产生,如何消除冗余代码是一个重点,也是一个难点。
数月后,20多个团队悉数切入到最新的框架,另一个令人头疼的问题马上又出来了,虽然大家样式都接入到最新的风格了,但是老的样式哪些能删?哪些不能删又是一个令人头疼的问题。
几个月前维护CSS同事嫌工资低了,换了一个同事维护全站基础css;再过了一段时间,组织架构调整,又换了一个同事维护;再过了一段时间,正在维护css的同事觉得自己级别低了,在公司内部等待晋级确实熬不住,于是也走了。这个基础css俨然变成了一笔烂账,谁也不敢删,谁也不愿意动,动一下错一下。
这个问题表面上看是一个css问题,其实这是一个前端难题,也是过度解耦,拆分机制不正确带来的麻烦。
CSS是前端不可分割的一部分,HTML模板与Javascript可以用requireJS处理,很大程度上解决了javascript变量污染的问题,css一般被一起分离了出来,单独存放。一个main.css包含全站重置的样式,表单、列表、按钮的基础样式,完了就是全站基础的UI组件。
总有业务团队在实际做项目时会不自主的使用main.css中的一些功能,如果只是使用了基础的重置还好,但是一旦真的使用其中通用的表单、列表等便2B了
main.css的初衷当然是将各个业务团队通用的部分提炼出来,事实上也该这样做,但理想很丰满,现实很残酷,不同的人对SEO、对语义化对命名的理解不太一样,换一个人就会换一套东西。第一批项目上线后,过了几个月,开发人员成长非常巨大,对原来的命名结构,完全不削一顾,自己倒腾出一套新的东西,让各个团队换上去,其它团队面对这种要求是及其头疼的,因为各个团队会有自己的CSS团队,这样一搞势必该业务团队的HTML结构与CSS要被翻新一次,这样的意义是什么,便不太明显了。2个星期过去了,新一批“规范化”的结构终于上线了,2个月后所有的业务团队全部接了新的结构,似乎皆大欢喜,但是那个同事被另一个团公司挖过去当前端leader了,于是一大群草泥马正在向业务团队的菊花奔腾过去!这里的建议是:
业务团队不要依赖于框架的任何dom结构与css样式,特别不要将UI组件中的dom结构与样式单独抠出来使用,否则就准备肥皂吧
CSS冗余的解决方案
① jQuery,解决IE时代令人头疼的兼容问题
② 移动浪潮,让HTML5与CSS3流行起来
③ requireJS,模块化加载技术让前端开发能协同作战,也一定限度的避免了命名污染
④ Hybrid,Hybrid技术将前端推向了一个前所未有的高度,这门技术让前端肆无忌惮的侵占着native的份额
如果说接下来会有一门技术会继续推动前端技术发展,有可能是web components,或者出现了新的设备。
web component是前端几项技术的融合,里面有一项功能为shadow dom,shadow dom是一种浏览器行为,他允许在document文档中渲染时插入一个独立的dom子树,但这个dom树与主dom树完全分离的,不会互相影响。以一个组件为例,是这个样子的:
一个组件就只有一个div了,这是一件很棒的事情,但实际的支持情况不容乐观:
然后web components还会有一些附带的问题:
① css与容器一起出现,而没有在一个文件中,在很多人看来很“奇怪”,我最初也觉得有点怪
② 大规模使用后,用于装载HTML的容器组件如何处理,仍然没有一个很好的方案
③ 对于不支持的情况如何做降级,如何最小化代码
④ 没有大规模使用的案例,至少国内没有很好的验证过
其中shadow dom**也是解决css重复的一个办法,以一个页面为例,他在原来的结构是这个样子的:
main.css
view1.js
view1.html
view2.js
view2.css
开发的时候是这个样子:
view1.css
view1.js
view1.html
最终发布是这个样子:
view1.js
这一切归功于requireJS与grunt打包工具,这里给一个实际的例子:
这里最终会被打包编译为一个文件:
这样的话版本UI升级只与js有关系,requireJS配置即可,这里只是UI的应用,很容易便可以扩展到page view级别,使用得当的话妈妈再也不用关心我们的版本升级以及css冗余了
这里处理降级时,会给css加前缀,如一个组件id为ui,其中的css会编译为
···css
···
由于css选择器是由右至左的,这种代码产生的搜索消耗是一个缺点,但是与尺寸的降低比起来便不算什么
网络请求
请求是前端优化的生命,优化到最后,优化到极致,都会在请求数、请求量上做文章,常用并且实用的手段有:
① CSS Sprites
② lazyload
③ 合并脚本js文件
④ localsorage
……
无论CDN还是Gzip,都是在传输上做文章,金无足赤,月无常圆,以上技术手段皆有其缺陷,是需要验证的,如何正确恰当的使用,我这里谈下我的理解
CSS Sprites
CSS Sprites可以有效的减低请求数,偶尔还可以降低请求量,但是随着发展,可能会有以下问题:
① 新增难,特别是css维护工作换人的情况下
② 删除难,这个问题更加明显,1年后,前端风格已经换了两批了,这里要知道哪些图标还在用,哪些没用变得非常困难
③ 调整难,一个图标刚开始是红色,突然需要变成蓝色,这类需求会让这个工作变得不轻松
④ 响应式,这个更会导致指数级的增长,背景图要随着宽度缩放这种需求更加讨厌
这里放一张做的很好的图:
由图所示,这里是对尺寸做了一定区分的,但是这里仍然不是最优,其实以上很多图标可以直接由CSS3实现,这里举两个案例:
http://iconfont.cn/repositories(svg)
http://codepen.io/saeedalipoor/pen/fgiwK(CSS3)
这里优劣之分各位自己判断,我反正完全偏向了CSS3……
每次http请求都会带上一些额外信息,比如cookie每次都会带上,上述的CSS Sprites的意义就是,当请求一个gzip后还不到1K的图标,搞不好请求数据比实际需求数据还大
而一次http还会导致其它开销,每次都会经历域名解析、开启连接、发送请求等操作,以一个图片请求在正常网速与2G情况来说:
可以看到,在网速正常的情况下,等待消耗的时间可能比传输还多,这个时候,CSS Sprites的意义就马上出来了,这里再说一个问题并行加载的问题。
我之前碰到一次图片加载阻塞js的案例,其出现原因就是浏览器并发数限制,这里以一个图为例:
chrome在请求资源下会有所限制,移动端的限制普遍在6个左右,这个时候在并发数被占满时,你的ajax便会被搁置,这在webapp中情况更加常见,所以网络限制的情况下请求数控制是必要的,而且可以降低服务器端的压力。
工作中实际使用的离线缓存有localstorage与Application cache,这两个皆是好东西,一个常用于ajax请求缓存,一个常用于静态资源缓存,这里简单说下我的一些理解。
首先localsorage有500万字符的限制,基本来说就是5M左右的限制,浏览器各有不同,也会有读写的性能损耗,所以不能毫无限制的使用
localstorage不被爬虫识别,不能跨域共享,所以不要用以存储业务关键信息,更加不要存储安全信息,要做到有,锦上添花;无,毫无影响才行:
① 500万字符限制
② 一般存储ajax请求返回数据,并且需要设置过期时间
③ 具有清理机制,将过期数据清理
④ 不存储敏感信息
⑤ 不存储SEO依赖数据,至少不能严重依赖
⑥ 隐私模式localstorage不可读写,所以不能用它来做页面通信
⑦ localstorage读写有性能损耗,大数据读写要避免
Application cache是HTML5新增api,虽然都是存储,却与localstorage、cookie不太相同,Application cache存储的是一般是静态资源,允许浏览器请求这些资源时不必通过网络,设计得当的情况可以取代Hybrid的存储静态资源,使用Application cache主要优点是:
使用Application cache可以提升网站载入速度,主要体现在请求传输上,把一些http请求转为本地读取,有效地降低网络延迟,降低http请求,使用简单,还节约流量何乐而不为?
而无论什么存储技术都会有空间限制(据说是5M),这里更新的机制是最为重要的,这里是我们使用的结论:
application cache是绝对值得使用的,是可以锦上添花。但怎么用,用多少是需要考虑的点。由于原理上,application cache是把manifest上的资源一起下载下来,所以manifest里的内容不宜过多,数据量不宜过大;由于manifest的解析通常以页面刷新为触发点,且更新的缓存不会立即被使用,所以缓存的资源应以静态资源、更新频率比较低的资源为主。另外要做好对manifest文件的管理,由于清单内文件不可访问或manifest更新不及时造成的一些问题。
除了真实手段优化代码处理尺寸,降低请求数,仍然有一些带有“欺骗”性质的技术可以做首页加载的优化,比如lazyload、fake页
我们常说的延迟加载是图片延迟加载,其实非图片也可延迟加载,看实际需求即可,这里点到即可,不再多说。
为img标签src设置统一的图片链接,而将真实链接地址装在自定义属性中。
所以开始时候图片是不会加载的,我们将满足条件的图片的src重置为自定义属性便可实现延迟加载功能
fake页
我们应该避免页面长时间白页,所以会出现fake页的概念,页面渲染仅仅需要HTML以及CSS,这个便是第一个优化点,js对于显示不是必须,ajax也不是。
若是任由js、ajax加载完成再渲染页面,用户很有可能失去耐心,所以搞一些内嵌的css以及通用的html在首页似乎是一个不错的选择
一个静态HTML页面,装载首屏的基本内容,让首页快速显示,然后js加载结束后会马上重新渲染整个页面,这个样子,用户就可以很快的看到页面响应,给用户一个快的错觉
这里的预加载是在浏览器空闲的时候加载后续页面所需资源,是一种浪费用户流量的行为,属于以空间换时间的做法,但是这个实施难度比较高。
预加载的前提是不影响主程序的情况下偷偷的加载,也就是在浏览器空闲的时候加载,但是浏览器空闲似乎变得不可控制
浏览器空闲不可判断(如果您知道请留言),我们判断的标准是当前没有dom事件操作,没有ajax
可以看出,由于浏览器没有空闲的回调,所以我们只能自己实现,这类的实现不太靠谱,我们的预加载做的就很粗暴,要做预加载需要注意以下几点:
① 浏览器空闲需要一个判断机制
② 每次空闲时需要有一个队列一点一点的加载资源,否则请求一旦发出很容易影响主逻辑
③ 做好预加载资源队列的匹配算法,可以是业务团队配置
移动革命——Hybrid
Hybrid技术将前端推到了前所未有的高度,但是Hybrid开发中本身也有一些需要注意的地方,这里如果出现了设计上的失误会对后期业务团队开发带问题,有几点可以注意
最初的app一般是native开发的,Hybrid依然依赖于native开发人员,但是请一定拒绝任何native为webview提供任何业务类UI,强势的对native说不!!!
最常见的的情况是,native为前端提供一个native的头,下面是一个webview装载html与css,这个是一件非常坑的事情
Hybrid中使用native的头,是我觉得最头疼的事情!!!
为什么会使用native的头呢?当时交涉的结果是:
① javascript容易报错,一旦出错,页面会陷入假死
② 进入webview时,页面有一个准备动作,资源由native取很快,由线上取很慢;无论如何会出现一段时间的白页
其实上述皆是可以解决的,Hybrid中会存在native头的主要原因还是防止页面乱写js出错,但是一般意义的app不是微信这类容器软件,里面的页面是开发人员经过严格测试写出来的,js出错会假死,native代码出错还会闪退呢。问题一,站不住脚,而且完全可以使用这种方式处理:
<header >
<a href="taobao://wireless">后退</a>
<h1>
标题
</h1>
</header>
就算是js报错,我这里假设一来就报错,处处报错,但以上协议native是一定可以捕捉的,js正确的情况便e.preventDefault(),错误便跳回首页,这个不是不可处理。
问题二其实与问题一一致,最初进入的时候明明可以有个可关闭的native loading,在webview加载好后再系统级别的关闭loading即可,没有什么不能解决的。
之所以我这里会如此激烈的拒绝native提供的头,是因为H5页面是一般是三套共用,H5站点,ios,android,而H5的dom操作千变万化,头部一些奇怪的需求展示,native根本无从支持,这里还会涉及跨团队合作,所以Hybrid开始的时候一定要坚决抵制native 提供的业务类UI,不然后期交流很麻烦。
你永远不能理解服务器端为什么会一次性给你那么多数据,所以你也不能理解设计一个好的Hybrid交互模型为什么这么难!程序员为什么总是互相伤害?
简单来说,Hybrid的交互非常简单,与ajax交互模型非常相似,这里以一张简单的交互图做说明:
交互的核心是native可以拿到webview的window对象,native可以拦截webview的http请求,于是native便可以干任何事情了
因为Hybrid拦截URL各有不同,IOS、android、winphone要做兼容,以window.location设置,创建iframe发出请求。但是,这段兼容的js代码一定不能交给native的同事写,必须自己写!否则500行代码可以解决的问题,你会发现半年后可能会洋洋洒洒变成几千行,因为他们不关注尺寸,不熟悉js....
我这里有一个简单的交互代码,可以参考:
Hybrid调用H5,直接拿到window对象,拿到对应方法即可,H5调用native方法略有不同,比如要拿手机通讯录可以这样做:
window.Hybrid = {};
//封装统一的发送url接口,解决ios、android兼容问题,这里发出的url会被拦截,会获取其中参数,比如:
//这里会获取getAdressList参数,调用native接口回去通讯录数据,形成json data数据,拿到webview的window执行,window.Hybrid['hybrid12334'](data)
···javascript
var bridgePostMessage = function (url) {
if (isIOS()) {
window.location = url;
} if (isAndriond()) {
var ifr = $('<iframe src="' + url + '"/>');
$('body').append(ifr);
}
};
//根据参数返回满足Hybrid条件的url,比如taobao://getAdressList?callback=hybrid12334
var _getHybridUrl = function (params) {
var url = '';
//...aa操作paramss生成url
return url;
};
//页面级用户调用的方法
var requestHybrid = function (params) {
//其它操作......
//生成唯一执行函数,执行后销毁
var t = 'hybrid_' + (new Date().getTime());
//处理有回调的情况
if (params.callback) {
window.Hybrid[t] = function (data) {
params.callback(data);
delete window.Hybrid[t];
}
}
bridgePostMessage(_getHybridUrl(params))
};
//h5页面开发,调用Hybrid接口,获取通讯录数据
define([], function () {
return function () {
//业务实际调用点
requestHybrid({
//native标志位
tagname: 'getAdressList',
//返回后执行回调函数
callback: function (data) {
//处理data,生成html结构,装载页面
}
});
}
});
当然这个代码比较简单,未做一些兼容一些处理,但是完全满足Hybrid交互模型,这里返回的json data再有处理,我们这里便可以设计success、error等回调。你完全想不到真实的js会到达几千行之巨,这些都是跨部门交流的让步与疼痛啊!
其实H5的调试就已经是一个老大难问题,Hybrid让这种场景变得更加复杂,chrome本身提供了一些移动端的调试方法,但是ios未越狱的话不好处理
而标准的公司中又会对ip有所限制,所以使用ip调试也比较麻烦,设置代理也费时费力,这个时候便需要更高级别的人站出来角力了,这块老大难问题不同公司还不一样,事实上我也犯难……
① ip调法,手机使用无线连接公司内网,使用手机浏览器打开网页,改一个代码,刷新一下,不行就代理,通不过就叫leader去推动安全部门开启特殊端口
② ios高端调法,具有Mac机情况下手机连接Safari可调速,我用过几次,但是由于没有mac机,实际步奏忘了...
③ android机低端调试,android可以直接开启root权限,使用chromeF12开发者工具调试
关于移动端调试的文章很多,各位去看看有用的吧……
事实证明多webview在低端android机上很卡,慎用。高端机多webview干的页面切换的活CSS3也能做,多webview意义不大
PS:来百度后,发现多webview卡的原因可能是native方的实现有问题,此段存疑
1 多webview与多iframe很类似,webview是一个很重的native空间,一上来就吃掉4M存储
2 单webview共享一个window对象,document共享,多webview通信机制有门槛,虽然localstorage共享,但通信依旧不方便
3 webview装载html依旧会有闪现的问题,跳转难度高
多webview的意义是:
① 很好的页面切换效果
② 释放javascript执行环境,以便降低内存
但是目的一依旧会闪,目的二使内存更加吃紧,费力不讨好
移动端会有一些不恰当的需求,这类需求看似无关重要,却会对整个移动框架造成隐患,甚至影响整体验。
移动端第一个恶心需求就算H5网页唤醒app操作,这个需求一般会出现在页面底部的广告栏,比如这个样子:
如果仅仅是唤醒app倒是简单,随之而来的需求是:
① H5站点检测是否安装app(尼玛js如何判断?),安装便打开,没安装便跳到下载页
② 需求变更,ios去AppStore,android强制下载
③ bug回归,android老是强制下载,希望可以判断,未安装才下载
......
总而言之,需求的核心难点就是,H5站点检测app是否安装,这个时候你要站出来大声的告诉产品:
① 纯粹js暂时无法判断app是否安装
② 前端只能做唤醒的工作或者跳到下载页的需求,强制下载什么类似需求请不予理睬
这个一般会有两个需求,点击浏览器回退关闭弹出层(框架提供的alert、toast、loading之类),点击android回退键关闭弹出层
如果碰到这个需求,我建议你还是直接拒绝掉,对于UI来说,这类操作会带来一个信号,js完成这个功能需要操作History
对于多页来说,这个功能还好点,对于单页来说,这个步骤便会破坏webapp耐以生存的History队列,伴随着可能是回退错乱,可能是中间页循环……
webapp的History本就很脆弱,这样一搞很容易出BUG,有信心处理好History问题的话去实现,否则还是算了吧……
全站IScroll化一般为了解决:
① fixed问题
② webapp中view独享“scrollTop”
③ webapp page 切换动画顺畅,因为scrollTop与长短页问题
④ 嫌弃原生的scroll不够平滑
这里还是不建议全站使用IScroll这类技术,IScroll可能带来,header消失、文本框消失、可视区域便小等问题,现在还是小范围弹出层使用就好,某天overflow: scroll兼容问题得到解决,区域滚动便不再难了。
这里倒不是一味抵制IScroll全站化,如果页面dom结构简单,如果页面文本框比较少,又做过充分调研,IScroll化带来的页面切换效果还是很赞的,正是道不虚行,只在人也。
文章浅谈了一些自己对移动端从开发到优化的一些建议,没有什么高深的知识,也许还有很多错误的地方,请各位不吝赐教,多多指点,这里总结一下几个比较重要的地方:
一 单页门槛高,体验好
二 移动框架,轻为王道
三 mvc业务框架最好自造
四 模块化(requireJS)必不可少
五 冗余是优化的敌人,无论网站速度还是代码维护
六 css解耦乃长远之计
七 零请求无流量是优化的最终手段
八 速度优化缓存为王
九 Hybrid带来移动革命,与native保持接口调用即可
十 坑大的需求还是拒绝算了......
结合网上相关资料,以及自己项目中的经验,收集汇总了iOS Webapp相关的开发知识,如下。
WebApp是一种新出现的基于WEB形式的类应用程序,运行在高端的移动终端设备上,其应用范围会越来越广。
开发者们都知道在高端智能手机系统中有两种应用程序:一种是基于本地(操作系统)运行的APP;一种是基于高端机的浏览器运行的WebApp,本文将主要讲解后者。
1、开发成本非常大。
一般使用的开发语言为JAVA、C++、Objective-C。
2、更新体验较差、同时也比较麻烦
每一次发布新的版本,都需要做版本打包,且需要用户手动更新(有些应用程序即使不需要用户手动更新,但是也需要有一个恶心的提示)。
3、非常酷
因为native app可以调用IOS中的UI控件以UI方法,它可以实现WebApp无法实现的一些非常酷的交互效果
4、Native app是被Apple认可的
Native app可以被Apple认可为一款可信任的独立软件,可以放在Apple Stroe出售,但是Web app却不行。
1、开发成本较低
使用html5 + CSS3 + js 等web开发技术就可以轻松的完成web app的开发。效果上面能够完全模拟传统应用程序效果。
2、升级较简单
由于不需要通过苹果商店发布,所以升级不需要通知用户,在服务端更新文件即可,用户完全没有感觉
3、维护比较轻松
和一般的web一样,维护比较简单,它其实就是一个站点
Webapp说白了就是一个针对Iphone、Android优化后的web站点,它使用的技术无非就是HTML或HTML5、CSS3、JavaScript,服务端技术JAVA、PHP、ASP。
当然,因为这些高端智能手机(Iphone、Android)的内置浏览器都是基于webkit内核的,所以在开发WEBAPP时,多数都是使用 HTML5和CSS3技术做UI布局。当使用HTML5和CSS3l做UI时,若还是遵循着一般web开发中使用HTML4和CSS2那样的开发方式的 话,这也就失去了WEBAPP的本质意义了,且有些效果也无法实现的,所以在此又回到了我们的主题–webapp的布局方式和技术。
在此所说的移动平台前端开发是指针对高端智能手机(如Iphone、Android)做站点适配也就是WebApp,并非是针对普通手机开发 Wap 2.0,所以在阅读本篇文章以前,你需要对webkit内核的浏览器有一定的了解,你需要对HTML5和CSS3有一定的了解。如果你已经对此有 所了解,那现在就开始往下阅读吧……
<meta content=”width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0;” name=”viewport” />
<meta content=”yes” name=”apple-mobile-web-app-capable” />
<meta content=”black” name=”apple-mobile-web-app-status-bar-style” />
<meta content=”telephone=no,email=no” name=”format-detection” />
第一个meta标签表示:强制让文档的宽度与设备的宽度保持1:1,并且文档最大的宽度比例是1.0,且不允许用户点击屏幕放大浏览;
user-scalable定义是否可缩放(0为不缩放),使页面固定设备上面的大小。
(注意:据说HTC G7自身系统浏览器不支持这一条规则,能对页面进行放大,一旦放大导致页面布局错乱,解决方法:定义页面的最小宽度 min-width,body{min-width: 300px;})
iOS 7.1的Safari为meta标签新增minimal-ui属性,在网页加载时隐藏地址栏与导航栏。
第二个meta标签是ios设备(不只iphone)中的safari私有meta标签,它表示:允许全屏模式浏览,开启对Web Aapp程序的支持。;
第三个meta标签也是ios系统的私有标签,它指定在web app状态下,ios设备中顶端的状态条的颜色; 默认值为default(白色),可以定为black(黑色)和black-translucent(灰色半透明)。若值为“black-translucent”将会占据页面px位置,浮在页面上方(会覆盖页面20px高度–iphone4和itouch4的Retina屏幕为40px)。
第四个meta标签表示:使设备浏览网页时对数字不启用电话功能(不同设备解释不同,itouch点击数字为存入联系人,iphone为拨打电话),忽略将页面中的数字识别为电话号码。
若需要启用电话功能将telephone=yes即可,具体调用格式可以这样书写代码Call Me,若在页面上面有google maps, iTunes和youtube的链接会在ios设备上打开相应的程序组件。
在开始编写webapp时,哥建议前端工程师使用HTML5,而放弃HTML4,因为HTML5可以实现一些HTML4中无法实现的丰富的WEB应用程序 的体验,可以减少开发者很多的工作量,当然了你决定使用HTML5前,一定要对此非常熟悉,要知道HTML5的新标签的作用。比如定义一块内容或文章区域 可使用section标签,定义导航条或选项卡可以直接使用nav标签等等。
在项目开发过程中可以会遇到内容排列排列显示的布局(见下图),假如你遇见这样的视觉稿,哥建议你放弃float,可以直接使用display:block;
这个按钮有圆角效果,有内发光效果还有高光效果,这样的按钮使用CSS3写是无法写出来的,当然圆角可以使用CSS3来写,但高光和内发光却无法使用 CSS3编写,这个时候你不妨使用-webkit-border-image来定义这个按钮的样式。-webkit-border-image就个很复杂 的样式属性。
请保证将每条数据都放在一个a标签中,为何这样做?因为在触控手机上,为提升用户体验,尽可能的保证用户的可点击区域较大。
在编写CSS时,我不建议前端工程师把容器(不管是外层容器还是内层)的宽度定死。为达到适配各种手持设备,我建议前端工程师使用自适应布局模式(支付宝 采用了自适应布局模式),因为这样做可以让你的页面在ipad、itouch、ipod、iphone、android、web safarik、 chrome都能够正常的显示,你无需再次考虑设备的分辨率。
上一节,我们说过自适应布局模式,有些同学可能会问:如何在移动设备上做到完全自适应呢?很感谢webkit为display属性提供了一个webkit-box的值,它可以帮助前端工程师做到盒子模型灵活控制。
看过iOS webapp API的同学都知道iOS提供了一个meta标签:用于禁用iOS对页面中电话号码的自动识别。在iOS中是不自动识别邮件地 址的,但在Android平台,它会自动检测邮件地址,当用户touch到这个邮件地址时,Android会弹出一个框提示用户发送邮件,如果你不想 Android自动识别页面中的邮件地址,你不妨加上这样一句meta标签在head中 1 <meta content=”email=no” name=”format-detection” />
你的老板或者PD或者交互设计师可能会要求你:能否让我们的webapp更加像nativeapp,我不想让用户看见那个输入url的控件条?
答案是可以做到的。我们可以利用一句简单的javascript代码来实现这个效果
1 setTimeout(scrollTo,0,0,0);
请注意,这句代码必须放在window.onload里才能够正常的工作,而且你的当前文档的内容高度必须是高于窗口的高度时,这句代码才能有效的执行。
我曾经也想禁止用户旋转设备,也想实现像某些客户端那样:只能在肖像模式或景观模式下才能正常运行。但现在我可以很负责任的告诉你:别想了!在移动版的webkit中做不到!
至少Apple webapp API已经说到了:我们为了让用户在safari中正常的浏览网页,我们必须保证用户的设备处于任何一个方位 时,safari都能够正常的显示网页内容(也就是自适应),所以我们禁止开发者阻止浏览器的orientationchange事件,看来苹果公司的出 发点是正确的,苹果确实不是一般的苹果。
iOS已经禁止开发者阻止orientationchange事件,那Android呢?对不起,我没有找到任何资料说Android禁止开发者阻止浏览器orientationchange事件,但是在Android平台,确实也是阻止不了的。
看过Apple webapp API的同学都知道iOS为safari提供了一个将当前页面添加主屏的功能,按下 iphoneipodipod touch底部工具中的小加号,或者ipad顶部左侧的小加号,就可以将当前的页面添加到设备的主屏,在设备的主屏会自动 增加一个当前页面的启动图标,点击该启动图标就可以快速、便捷的启动你的webapp。从主屏启动的webapp和浏览器访问你的webapp最大的区别 是它清除了浏览器上方和下方的工具条,这样你的webapp就更加像是nativeapp了,还有一个区别是window对像中的navigator子对 象的一个standalone属性。iOS中浏览器直接访问站点时,navigator.standalone为false,从主屏启动webapp 时,navigator.standalone为true, 我们可以通过navigator.standalone这个属性获知用户当前是否是从主屏访 问我们的webapp的。在Android中从来没有添加到主屏这回事!
我们知道在iOS中,当虚拟键盘弹出时,默认情况下键盘是开启首字母大写的功能的,根据某些业务场景,可能我们需要关闭这个功能,移动版本webkit为 input元素提供了autocapitalize属性,通过指定autocapitalize=”off”来关闭键盘默认首字母大写。
有时我们可能需要禁止用户在新窗口打开页面,我们可以使用a标签的target=”_self“来指定用户在新窗口打开,或者target属性保持空,但 是你会发现iOS的用户在这个链接的上方长按3秒钟后,iOS会弹出一个列表按钮,用户通过这些按钮仍然可以在新窗口打开页面,这样的话,开发者指定的 target属性就失效了,但是可以通过指定当前元素的-webkit-touch-callout样式属性为none来禁止iOS弹出这些按钮。这个技 巧仅适用iOS对于Android平台则无效。
我们在第13条技巧中提到元素的-webkit-touch-callout属性,同样为一个img标签指定-webkit-touch-callout为none也会禁止设备弹出列表按钮,这样用户就无法保存\复制你的图片了。
我们通过指定文字标签的-webkit-user-select属性为none便可以禁止iOS用户选中文字。
桌面浏览器中想要获取滚动条的值是通过document.scrollTop和document.scrollLeft得到的,但在iOS中你会发现这两 个属性是未定义的,为什么呢?因为在iOS中没有滚动条的概念,在Android中通过这两个属性可以正常获取到滚动条的值,那么在iOS中我们该如何获 取滚动条的值呢?
通过window.scrollY和window.scrollX我们可以得到当前窗口的y轴和x轴滚动条的值。
当你指定了一个块级元素时,并且为其定义了边框,设置了其宽度为100%。在移动设备开发过程中我们通常会对文本框定义为宽度100%,将其定义为块级元 素以实现全屏自适应的样式,但此时你会发现,该元素的边框(左右)各1个像素会溢了文档,导致出现横向滚动条,为解决这一问题,我们可以为其添加一个特殊 的样式-webkit-box-sizing:border-box;用来指定该盒子的大小包括边框的宽度。
如果大家够细心的话,在做wap站点开发时,大家应该会发现android 2.0以下的平台中问题特别的多,比如说边框圆角这个问题吧。
在对一个元素定义圆角时,为完全兼容android 2.0以下的平台,我们必须要按照以下技巧来定义边框圆角:
1\-webkit这个前缀必须要加上(在iOS中,你可以不加,但android中一定要加);
2\如果对针对边框做样式定义,比如border:1px solid #000;那么-webkit-border-radius这属性必须要出现在border属性后。
3\假如我们有这样的视觉元素,左上角和右上角是圆角时,我们必须要先定义全局的(4个角的圆角值)-webkit-border- radius:5px;然后再依次的覆盖左下角和右下角,-webkit-border-bottom-left-radius:0;-webkit- border-bottom-right-border:0;否则在android 2.0以下的平台中将全部显示直角,还有记住!-webkit这个前 缀一定要加上!
虽然你的html和css都是完全自适应的,但有一天如果你发现你的页面在android中显示的并不是自适应的时候,首先请你确认你的head标签中是否包含以下meta标签:
1 <meta name=”viewport” content=”width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0;” />
如果有的话,那请你再仔细的看清楚有没有这个属性的值width=device-width,如果没有请立即加上吧!
新的iOS系统也就是4.3版本,升级后对safari造成了一个bug:即使你添加了如下的meta标签,safari仍然会对页面中的5位连续的数字进行自动识别,并且将其重新渲染样式,也就是说你的css对该标签是无效的。
<meta name=”format-detection” content=”telphone=no” />
我们可以用一个比较龌龊的办法来解决。比如说支付宝wap站点中显示金额的标签,我们都做了如下改写:
1 <button class=”t-balance”style=”background:none;padding:0;border:0;”>95009.00元
iPhone 4带来的革新,retina display绝对是最吸引眼球的一项。正是依赖这视网膜显示屏,iPhone 4的分辨率达到了640×960 pixels,不过为了保持向下兼容性,它采用的仍然是320×480 points。也就是说,在不进行缩放的情况下,显示普通图片时,它会用4个像素来显示图片中的1个像素;而在显示retina图片时,每个像素都对应图片中的1个像素。
如此一来,老的应用无需修改就可以在iPhone 4上运行了——虽然显示效果差了点,但是不会出现只有左上角那1/4的区域有内容的情况。
iOS 6发布有许多新的变化,比如smart app banner功能,新版iPhone屏幕也由3:2变长为16:9,分辨率从iPhone 4s的640px_960变成了640_1136,DPI依然是326。平时我们习惯用useragent检测,返回:
Mozilla/5.0 (iPhone; CPU iPhone OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0
但是,这个方法无法分辨是刷机升级到iOS 6的低版本iPhone,还是最新的原装iPhone 5,这里用javascript或者通过 CSS3 的 Media Queries 特性进行区分。
在网页中,pixel与point比值称为device-pixel-ratio,普通设备都是1,iPhone 4是2,有些Android机型是1.5。
那么-webkit-min-device-pixel-ratio:2可以用来区分iphone(4/4s/5)和其它的手机
iPhone4/4s的分辨率为640*960 pixels,DPI为是326,设备高度为480px
iPhone5的分辨率为640*1136 pixels,DPI依然是326,设备高度为568px
那么我们只需要判断iphone手机的device-height(设备高)值即可区别iPhone4和iPhone5
方式一,直接写到样式里面
/*由于isPhone4inches = (window.screen.height==568);*/
@media (device-height:480px) and (-webkit-min-device-pixel-ratio:2){/* 兼容iphone4/4s */
.class{}
}
@media (device-height:568px) and (-webkit-min-device-pixel-ratio:2){/* 兼容iphone5 or iPod Touch 5th generation*/
.class{}
}
方式二,链接到一个单独的样式表,把下面的代码放在标签里
<link rel="stylesheet" media="(device-height: 480px) and (-webkit-min-device-pixel-ratio:2)" href="iphone4.css" />
<link rel="stylesheet" media="(device-height: 568px)and (-webkit-min-device-pixel-ratio:2)" href="iphone5.css" />
使用JS
//通过高度来判断是否是iPhone 4还是iPhone 5
isPhone4inches = (window.screen.height==480);
isPhone5inches = (window.screen.height==568);
如果你之前通过apple-mobile-web-app-capable这个meta标签来将网页添加到主屏幕的话,这种方法只支持iPhone 的3.5〃屏幕,而iPhone 5会比较悲催,页面顶部和顶部会出现一条黑色区域。即便你提供了一个大尺寸的启动界面(640_1096),iPhone 5依然会将其压缩至640_920。
对iOS6的解决方案:
你需要放弃之前使用的viewport属性width=device-width或者width=320。如果你不指定viewport,它也可以很正常的显示:
<meta name="viewport" content="initial-scale=1.0">
或者你也可以指定一个非320的宽度:
<meta name="viewport" content="width=320.1">
如果你不想影响i
(function($,undefined){
$.fn.dragDrop = function(options,callback){
var defaults ={
focusElement:null, //点击哪个元素开始拖动,可为空。不为空时,需要为被拖动元素的子元素。
dir:'all', //拖动方向:['all','vertical','horizontal']
dirArea:null, //限制在哪个区域拖动,以数组形式提供[minX,maxX,minY,maxY]
dirMax:200 //单向拖动的最大值,单位PX
};
if(typeof(options)=="function"){
options={};
callback=options;
}
var opts = $.extend({},defaults,options),
bDraging = false,
moveElement = $(this),
ew=moveElement.outerWidth(),
eh=moveElement.outerHeight();
//点击哪个元素,以触发移动。
//该元素需要是被移动元素的子元素(比如标题等)
var focusElement = opts.focusElement ? $(opts.focusElement,moveElement) : moveElement ;
if(!focusElement || focusElement.length<=0){
throw new Error('focusElement is not found! the element must be a child of '+this.id);
return false;
}
// statX|Y : 初始时,鼠标与被移动元素原点的距离
// moveX|Y : 移动时,被移动元素定位位置 (新鼠标位置与statX|Y的差值)
var dragParams = {statX:0,statY:0,moveX:0,moveY:0};
if(opts.dir=="all"){
moveElement.css({'position':'absolute','left':0,'top':0});
}else{
moveElement.css({'position':'absolute','left':-ew/2,'top':0});
}
function move(v){
//dirArea格式: [左上角x,左上角Y,左下角x,左下角Y]
if(v!=undefined){
if(opts.dir=='all'){
dragParams.moveX= v.x;
dragParams.moveY= v.y;
}else if(opts.dir=='vertical'){
dragParams.moveY=v;
}else{
dragParams.moveX=v;
}
}
if(opts.dir=='all'){
if(opts.dirArea){
if(dragParams.moveX<opts.dirArea[0]){
dragParams.moveX=opts.dirArea[0]
}
if(dragParams.moveX>opts.dirArea[2]-ew){
dragParams.moveX=opts.dirArea[2]-ew
}
if(dragParams.moveY<opts.dirArea[1]){
dragParams.moveY=opts.dirArea[1]
}
if(dragParams.moveY>opts.dirArea[3]-eh){
dragParams.moveY=opts.dirArea[3]-eh
}
}
}else if (opts.dir=='vertical'){
if(dragParams.moveY<-eh/2){
dragParams.moveY=-eh/2;
}
if(dragParams.moveY>opts.dirMax-eh/2){
dragParams.moveY=opts.dirMax-eh/2;
}
}else{
if(dragParams.moveX<-ew/2){
dragParams.moveX=-ew/2;
}
if(dragParams.moveX>opts.dirMax-ew/2){
dragParams.moveX=opts.dirMax-ew/2;
}
}
if(opts.dir=='all'){
moveElement.css({'left':dragParams.moveX,'top':dragParams.moveY});
}else if (opts.dir=='vertical'){
moveElement.css({'top':dragParams.moveY});
}else if(opts.dir=='horizontal'){
moveElement.css({'left':dragParams.moveX});
}
}
function mouseDown(e){
bDraging = true;
//改变鼠标形状
if(opts.dir=='all'){
moveElement.removeClass('move');
}else{
moveElement.removeClass('w-resize');
}
moveElement.addClass('move');
//捕获事件。(该用法,还有个好处,就是防止移动太快导致鼠标跑出被移动元素之外)
if(moveElement.get(0).setCapture){
moveElement.get(0).setCapture();
}
dragParams.statX = e.pageX - moveElement.position().left;
dragParams.statY = e.pageY - moveElement.position().top;
return false;
}
function mouseMove(e){
if(bDraging){
dragParams.moveX = e.pageX - dragParams.statX;
dragParams.moveY = e.pageY - dragParams.statY;
move();
if(typeof(callback)==='function'){
if(opts.dir=='all'){
callback(dragParams);
}else if(opts.dir=='vertical'){
callback(dragParams.moveY+eh/2);
}else{
callback(dragParams.moveX+ew/2);
}
}
}
}
function mouseUp(e){
bDraging=false;
moveElement.css({'cursor':'default'});
moveElement.removeClass('move');
if(moveElement.get(0).releaseCapture){
moveElement.get(0).releaseCapture();
}
}
function enable(){
focusElement = opts.focusElement ? $(opts.focusElement,moveElement) : moveElement ;
focusElement.unbind('mousedown.silder').bind('mousedown.silder',mouseDown);
if(!focusElement.get(0).setCapture){
focusElement=$(document);
}
focusElement.unbind('mousemove.silder').bind('mousemove.silder',mouseMove);
focusElement.unbind('mouseup.silder').bind('mouseup.silder',mouseUp);
}
function disable(){
focusElement = opts.focusElement ? $(opts.focusElement,moveElement) : moveElement ;
focusElement.unbind('mousedown.silder');
if(!focusElement.get(0).setCapture){
focusElement=$(document);
}
focusElement.unbind('mousemove.silder');
focusElement.unbind('mouseup.silder');
}
enable();
return {
val:function(v){
if(typeof(v)==undefined){
if (opts.dir=='vertical'){
return dragParams.moveY+eh/2
}else if(opts.dir=='horizontal'){
return dragParams.moveX+ew/2
}else{
return {x:dragParams.moveX,y:dragParams.moveY}
}
}else{
move(v)
}
},
enable:function(){
enable();
},
disable:function(){
disable();
}
}
return this;
};
$.fn.slider=function(options,callback){
var defaults ={
parent:$(this).parent(), //滑块的区域范围
highObj:$(this).parent().find('.high'), //高亮选中区域
dir:'horizontal',//拖动方向:['vertical','horizontal']
initVal:0,
min:0,
max:100
};
if(typeof(options)=="function"){
callback=options;
options={};
}
var opts = $.extend({},defaults,options),
ew=$(this).outerWidth(),
eh=$(this).outerHeight(),
width=opts.parent.width(),
height=opts.parent.height(),
max=opts.max,
min=opts.min,
returnValue,dragObj;
if(opts.dir=='horizontal'){
dragObj=$(this).dragDrop({dir:opts.dir,dirMax:width},function(d){
returnValue=evalNum('width',d);
callback(returnValue);
})
}else{
dragObj=$(this).dragDrop({dir:opts.dir,dirMax:height},function(d){
returnValue=evalNum('height',d);
callback(returnValue);
})
}
$(defaults.parent).bind('click',function(e){
var x= e.pageX-$(defaults.parent).offset().left,
y= e.pageY-$(defaults.parent).offset().top;
if(opts.dir=='horizontal'){
setVal(x);
}else{
setVal(y);
}
});
function evalNum(arrow,d){
var ar=arrow=='width'?width:height,
num=(d/ar*(max-min+1)+min).toFixed(0);
num=num-max>=0?max:(num-min<=0?min:num);
opts.highObj.css(arrow,d);
return num
}
setVal(opts.initVal);
function setVal(v,bl){
if(opts.dir=='horizontal'){
v=v>=width?width:v;
dragObj.val(v-ew/2);
v=evalNum('width',v);
}else{
v=v>=height?height:v;
dragObj.val(v-eh/2)
v=evalNum('height',v);
}
if(bl!=1)
callback(v);
}
return {
val:function(v){
if(typeof(v)==undefined){
return returnValue;
}else{
if(opts.dir=='horizontal'){
v=v/(max-min+1)*width;
}else{
v=v/(max-min+1)*height
}
setVal(v,1)
}
},
enable:function(){
dragObj.enable();
},
disable:function(){
dragObj.disable();
}
}
}
})(jQuery);
开发手机大图浏览插件过程中,发现:
给图片设定
css({width:'auto',height:'100%'});
但设置
css({width:'100%',height:'auto'});
会造成出错
必须写成:
var h=$(window).height()
css({width:'auto',height:h});
可以修改以上的BUG
Backbone.Router.extend({
initialize:function(){
this.bind('route',function(){
console.log(1)
});
},
routes:{
"":"home"
},
home:function(){
console.log(2)
}
});
以上是backbone的Router简单实例,
页面访问时,先输出2,在输出1,
这个顺序不是很符合页面的执行逻辑;
route: function(route, name, callback) {
if (!_.isRegExp(route)) route = this._routeToRegExp(route);
if (_.isFunction(name)) {
callback = name;
name = '';
}
if (!callback) callback = this[name];
var router = this;
Backbone.history.route(route, function(fragment) {
var args = router._extractParameters(route, fragment);
callback && callback.apply(router, args);
router.trigger.apply(router, ['route:' + name].concat(args));
router.trigger('route', name, args);
Backbone.history.trigger('route', router, name, args);
});
return this;
},
页面会触发router事件后,会执行上述代码;
第11行代码会触发router扩展中定于的home函数;
第13行代码会执行我们在initialize函数中绑定的router函数。
参考JQuery的each函数的做法,我们可以适当的修改上述的router函数的执行顺序,并增加条件判断,实现不符合要求的router不执行home函数。
route: function(route, name, callback) {
if (!_.isRegExp(route)) route = this._routeToRegExp(route);
if (_.isFunction(name)) {
callback = name;
name = '';
}
if (!callback) callback = this[name];
var router = this;
Backbone.history.route(route, function(fragment) {
var args = router._extractParameters(route, fragment);
router.trigger.apply(router, ['route:' + name].concat(args));
if(router.trigger('route', name, args)!==false) callback && callback.apply(router, args);
Backbone.history.trigger('route', router, name, args);
});
return this;
}
var date=new Date();
date.getTime()
new Date().getime()
+new Date
但是这个写法在JS压缩时会出错。
function passwordLevel(str,num) {
var len=str.length,level,num=num||6;
if(!/\s/g.test(str)){
level=passwordFormat(str)
return level==0?0:(len>=num && level>2)?level:(len>num?level:(level>2?--level:1))
}
else{
return 0
}
}
function passwordFormat(str){
var i=0,ret = str.match(/^(\d+|[a-z]+)$|^([a-zA-Z0-9]+)$|^([\w~!@#$%^&*_]+)$/);
if(ret){
while (ret[++i] === undefined);
return i;
}else{
return 0
}
}
console.log(passwordLevel(" ")); //0
console.log(passwordLevel(" 13123"));//0
console.log(passwordLevel("123")); //1
console.log(passwordLevel("sss21312"));//2
console.log(passwordLevel("123xx"));//1
console.log(passwordLevel("123xxZ"));//1
console.log(passwordLevel("众收到货"));//0
console.log(passwordLevel("%$#@12"));//3
console.log(passwordLevel("%$#@1"));//2
function showallselect(id){
var select = document.getElementById(id);
if(select && select.options && select.options.length >1){
select.size = select.options.length;
}
}
···
最近在开发SPA页面,采用H5的history API,每个view都有各自独立的页面地址;
在测试微信传图和录音功能时发现,页面A初始化微信API认证后,在A页面中可以正常传图和录音,但切换到B页面,功能就不正常了,通过打印错误信息,发现是认证失败,这个问题只有安卓系统才有,而ios系统的微信没有这个问题。
为了解决这个问题,SPA页面每次切换都需要重新认证微信JDK
众所周知,对于一些相对小的站点来说,数据量并不是很大,select和update就可以解决我们面对的问题,本身负载量不是很大,最多再加几个 索引就可以搞定。对于大型网站,每天的数据量可能就上百万,如果一个设计不好的多对多关系,在前期是没有任何问题的,但是随着用户的增长,数据量会是几何 级的增长的。在这个时候我们对于一个表的select和update的时候(还不说多表联合查询)的成本的非常高的。
在一些时候,2.0的CTO都有个尚方宝剑,就是缓存。对于缓存,在高并发高处理的时候也是个大问题。在整个应用程序下,缓存是全局共享的,然而在 我们进行修改的时候就,如果两个或者多个请求同时对缓存有更新的要求的情况下,应用程序会直接的死掉。这个时候,就需要一个好的数据并发处理策略以及缓存 策略。
另外,就是数据库的死锁问题,也许平时我们感觉不到,死锁在高并发的情况下的出现的概率是非常高的,磁盘缓存就是一个大问题。
对于一些支持文件上传的2.0的站点,在庆幸硬盘容量越来越大的时候我们更多的应该考虑的是文件应该如何被存储并且被有效的索引。常见的方案是对文 件按照日期和类型进行存贮。但是当文件量是海量的数据的情况下,如果一块硬盘存贮了500个G的琐碎文件,那么维护的时候和使用的时候磁盘的Io就是一个 巨大的问题,哪怕你的带宽足够,但是你的磁盘也未必响应过来。如果这个时候还涉及上传,磁盘很容易就over了。
也许用raid和专用存贮服务器能解决眼下的问题,但是还有个问题就是各地的访问问题,也许我们的服务器在北京,可能在云南或者**的访问速度如何解决?如果做分布式,那么我们的文件索引以及架构该如何规划。
所以我们不得不承认,文件存贮是个很不容易的问题
我们可以很容易的规划出一个符合第三范式的数据库,里面布满了多对多关系,还能用GUID来替换INDENTIFY COLUMN 但是,多对多关系充斥的2.0时代,第三范式是第一个应该被抛弃的。必须有效的把多表联合查询降到最低。
众所周知,索引是提高数据库效率查询的最方面最廉价最容易实现的方案。但是,在高UPDATE的情况下,update和delete付出的成本会高的无法想想,笔者遇到过一个情况,在更新一个聚焦索引的时候需要10分钟来完成,那么对于站点来说,这些基本上是不可忍受的。
索引和更新是一对天生的冤家,问题A,D,E这些是我们在做架构的时候不得不考虑的问题,并且也可能是花费时间最多的问题,
对于2.0网站由于其高互动性,CDN实现的效果基本上为0,内容是实时更新的,我们常规的处理。为了保证各地的访问速度,我们就需要面对一个绝大的问题,就是如何有效的实现数据同步和更新,实现各地服务器的实时通讯有是一个不得不需要考虑的问题。
成也AJAX,败也AJAX,AJAX成为了主流趋势,突然发现基于XMLHTTP的post和get是如此的容易。客户端get或者post 到服务器数据,服务器接到数据请求之后返回来,这是一个很正常的AJAX请求。但是在AJAX处理的时候,如果我们使用一个抓包工具的话,对数据返回和处 理是一目了然。对于一些计算量大的AJAX请求的话,我们可以构造一个发包机,很容易就可以把一个webserver干掉。
对于HTTP协议来说,数据包都是明文传输的,也许我们可以说我们可以用加密啊,但是对于G问题来说的话,加密的过程就可能是明文了(比如我们知道 的QQ,可以很容易的判断他的加密,并有效的写一个跟他一样的加密和解密方法出来的)。当你站点流量不是很大的时候没有人会在乎你,但是当你流量上来之 后,那么所谓的外挂,所谓的群发就会接踵而来(从qq一开始的群发可见端倪)。也许我们可以很的意的说,我们可以采用更高级别的判断甚至HTTPS来实 现,注意,当你做这些处理的时候付出的将是海量的database,io以及CPU的成本。对于一些群发,基本上是不可能的。笔者已经可以实现对于百度空 间和qq空间的群发了。大家愿意试试,实际上并不是很难。
当我们的一台databaseserver不堪重负的时候,这个时候我们就需要做基于数据库的负载和集群了。而这个时候可能是最让人困扰的的问题 了,数据基于网络传输根据数据库的设计的不同,数据延迟是很可怕的问题,也是不可避免的问题,这样的话,我们就需要通过另外的手段来保证在这延迟的几秒或 者更长的几分钟时间内,实现有效的交互。比如数据散列,分割,内容处理等等问题
Openapi已经成为一个不可避免的趋势,从google,facebook,myspace到海内校内,都在考虑这个问题,它可以更有效的留住 用户并激发用户的更多的兴趣以及让更多的人帮助你做最有效的开发。这个时候一个有效的数据共享平台,数据开放平台就成为必不可少的途径了,而在开放的接口 的情况保证数据的安全性和性能,又是一个我们必须要认真思考的问题了。
内容分发网络(CDN)是一种新型网络构建方式,它是为能在传统的IP网发布宽带丰富媒体而特别优化的网络覆盖层;而从广义的角度,CDN代表了一种基于质量与秩序的网络服务模式。 在企业市场,大型互联网网站成为CDN网络的青睐者。新浪、搜狐、腾讯等大型门户网站以及淘宝都采用了第三方的CDN加速服务。目前,蓝汛、网宿是国内领先的CDN服务提供商,建设了遍布全国范围的CDN网络节点。
.pie { width:200px; height:200px; background-color:blue; border-radius:100px; position:absolute; }
.pie1 { clip:rect(0px,200px,200px,100px); -o-transform:rotate(0deg); -moz-transform:rotate(0deg); -webkit-transform:rotate(0deg); background:-moz-radial-gradient(20% 50% 0deg, #333, #0000ff); background:-webkit-gradient(radial, 100 100, 0, 100 100, 110, from(#000), to(#0000ff)); }
.pie2 { clip:rect(0px,100px,200px,0px); -o-transform:rotate(0deg); -moz-transform:rotate(0deg); -webkit-transform:rotate(0deg); background:-moz-radial-gradient(80% 50% 0deg, #333, #0000ff); background:-webkit-gradient(radial, 100 100, 0, 100 100, 110, from(#000), to(#0000ff)); }
.hold { width:200px; height:200px; position:absolute; z-index:1; }
.hold1 { clip:rect(0px,200px,200px,100px); }
.hold2 { clip:rect(0px,100px,200px,0px); }
.bg { width:200px; height:200px; background-color:red; border-radius:100px; position:absolute; box-shadow:0px 0px 8px #333; background:-moz-radial-gradient(0% 50% 0deg, #900, #ff0000); background:-webkit-gradient(radial, 100 100, 0, 100 100, 110, from(#900), to(#ff0000)); }
.time { width:160px; height:160px; margin:20px 0 0 20px; background-color:#fff; border-radius:100px; position:absolute; z-index:1; box-shadow:0px 0px 8px #333 inset; text-align:center; line-height:160px; font-family:"Book Antiqua", Palatino, serif; font-size:35px; font-weight:bold; text-shadow: 1px 1px 3px #333; }
.time em { background:#fff; position:absolute; top:62px; left:12px; height:18px; width:140px; opacity:0.4; }
<div class="hold hold1">
<div class="pie pie1" id="pie1"></div>
</div>
<div class="hold hold2">
<div class="pie pie2" id="pie2"></div>
</div>
<div class="bg"> </div>
<div class="time"><span></span><em></em></div>
依赖jquery
(function(step,total){
var count= 0,pos=0;
function start1(){
count += step;
count=count>=total?total:count;
if(count<=180){
$(".pie1").css("-o-transform","rotate(" + count + "deg)");
$(".pie1").css("-moz-transform","rotate(" + count + "deg)");
$(".pie1").css("-webkit-transform","rotate(" + count + "deg)");
if(count>=total){
return;
}
}else if(count<360){
if(!pos){
$(".pie1").css("-o-transform","rotate(" + 180 + "deg)");
$(".pie1").css("-moz-transform","rotate(" + 180 + "deg)");
$(".pie1").css("-webkit-transform","rotate(" + 180 + "deg)");
pos=1;
}
$(".pie2").css("-o-transform","rotate(" + (count-180) + "deg)");
$(".pie2").css("-moz-transform","rotate(" + (count-180) + "deg)");
$(".pie2").css("-webkit-transform","rotate(" + (count-180) + "deg)");
if(count>=total){
return;
}
}else{
$(".pie2").css("-o-transform","rotate(" + 180 + "deg)");
$(".pie2").css("-moz-transform","rotate(" + 180 + "deg)");
$(".pie2").css("-webkit-transform","rotate(" + 180 + "deg)");
}
if(count<360){
setTimeout(start1,100);
}
}
start1();
})(11,100)
function template(text, data) {
var settings = {
evaluate : /<%([\s\S]+?)%>/g,
interpolate : /<%=([\s\S]+?)%>/g,
escape : /<%-([\s\S]+?)%>/g
},
noMatch = /(.)^/,
escapes = {
"'": "'",
'\\': '\\',
'\r': 'r',
'\n': 'n',
'\u2028': 'u2028',
'\u2029': 'u2029'
},
escaper = /\\|'|\r|\n|\u2028|\u2029/g,
escapeChar = function(match) {
return '\\' + escapes[match];
},
matcher = RegExp([
(settings.escape || noMatch).source,
(settings.interpolate || noMatch).source,
(settings.evaluate || noMatch).source
].join('|') + '|$', 'g'),
index = 0,
source = "__p+='";
text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
source += text.slice(index, offset).replace(escaper, escapeChar);
index = offset + match.length;
if (escape) {
source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
} else if (interpolate) {
source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
} else if (evaluate) {
source += "';\n" + evaluate + "\n__p+='";
}
return match;
});
source += "';\n";
if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
source = "var __t,__p='',__j=Array.prototype.join," +
"print=function(){__p+=__j.call(arguments,'');};\n" +
source + 'return __p;\n';
try {
var render = new Function(settings.variable || 'obj', '_', source);
} catch (e) {
e.source = source;
throw e;
}
var template = function(data) {
return render.call(this, data, "");
};
if(data) return render(data);
var argument = settings.variable || 'obj';
template.source = 'function(' + argument + '){\n' + source + '}';
return template;
};
所有函数都是基于jquery的扩展;
checkNetwork 检测网络,只执行基本的网络状态识别;
isRetina 识别retina屏
uniqID 生产唯一id的方法,重复概率为万分之1~3
isWeixin 识别是否为微信
isIOS 识别ios系统
isAndroid 识别android系统
isMobile 检测国内手机号
charLen 返回字符串的长度,单位字符
setCache 设置系统缓存,默认有效期1天
getCache 读取缓存
textAreaScroll 输入框textarea滚动
scrollTo 滚动到指定元素
openAPP app自定义吊起,并下载
setCursorPosition 设置输入框光标的位置,兼容IE
getLimitString 截取指定长度的字符串
拥有3d transform属性
使用animation, transition实现opacity, transform的动画
使用CSS filters的元素
使用 will-change
gif图即使被其他Layer盖住不可见,也可能导致paint,不需要时应将gif图的display属性设为none。
为引起大范围Paint的元素生成独立的Layer以减小Paint的范围
可以采用fastDom.JS 优化
动画开始滚动后通过event-pointer: none禁止鼠标行为,减少不必要的repaint。
避免在动画过程中触发垃圾回收
不但改变CSS可能导致Layout,读取位置大小相关得属性也会导致Layout
分离读写,减少Layout
简化绘制的复杂度
避免不必要的绘制
减少绘制区域
最近研究了一下在浏览器中实现的 WYSIWYG 文本编辑器的原理, 在了解基本原理并浏览了 zenpen 这个相对简单的在线编辑器的源码后, 在这方面有种豁然开朗的感觉。
说来让人惊讶,最初在浏览器中使之变为可能的浏览器是 IE5。在那个时代, IE 的确也算是非常先进的浏览器了,现在广为使用的 AJAX 技术,不也是 IE5 最早提供的么? 不过这里就不再讨论当初 IE 那套陈旧的 API 了,而主要来讨论 HTML5 之后被各个浏览器广泛支持的一些技术方法。
使得某一部分 DOM 可以被编辑
可以获取和操作用户选中的区域
可以在编辑的同时对所编辑的部分 DOM 进行修改,实现如添加样式等功能
这些功能都非常方便实现。
使 DOM 可编辑
使一部分 HTML 的 DOM 作为容器进入编辑状态,只需要为这个 DOM 添加一个 contentEditable 属性。 一般来讲,是使用div或者article元素作为这样的容器。
被添加 contentEditable 属性的容器元素的子元素都就可以由用户修改了, 如果想在这个容器下面嵌套一个不可修改的子元素,需要显式地在这个子元素中添加 contentEditable=’false’这样的声明。
操作和获取用户选择是一个非常有用的功能,它不但可以用来实现这里提出的编辑器的功能, 还可以用来实现在光标位置显示提示选单等多种功能,在后面对所编辑部分进行样式修改的时候也常用到。
想要获取一个 Selection 对象非常简单:
var selection = window.getSelection();
Selection 对象有anchorNode和focusNode两个属性,可以用来获得选中部分的开始和结束元素, 不过实用不多(一般实用 Range 代替)。此外还有一个isCollapsed 属性值得注意,当其为true时,代表选择区域开始和结束在相同的点,也就是没有选中内容时光标闪烁的模式。
selection 对象还有诸多方法可以用来修改选择范围,主要就是对 Range 对象的编辑。
首先可以通过
var range = selection.getRangeAt(0);
来获取被选中的第一个区块,以此类推还可以获得第二个、第三个。简单起见我们只讨论选中一个区块的情况。 获得了 Range 对象,我们就可以方便地进行获得选中区域内容了。 Range 对象有一对属性分别用来获得选择区域的开始和结束点。
range.startContainer // 开始点所在的容器(元素)
range.endOffset // 开始点在其容器内的偏移
range.endContainer // 结束点所在的容器(元素)
range.endOffset // 结束点在其容器内的偏移
除了这两对属性,还有一个非常有用的属性,那就是
range.commonAncestorContainer // 选择范围的共同父元素
上面这个属性常用于检测选择范围的类型/样式,比如检测到选中范围的公共父元素是一个 h1 元素, 那么可以在工具栏中将代表 h1 元素的按钮设为激活状态。
需要注意的是,返回的 Range 对象是一类可变对象。简单来说,如果用户的选区改变了, 那么 Range 对象的内容也会改变。因此要记录某一时刻的 Range ,就要记录上面提到了的两对属性。 此外,Range 对象的属性都是只读的,需要通过对应的函数方法来修改。
Range 还定义了各种获取内容和修改内容的函数,详细参数和方法可以参见文档, 这里对几个常见的 Use Case 说明一下。
我们可以获得光标在网页上的精确位置,对于选区还能得到其矩形边框的几何表示, 这为我们显示提示菜单提供了方便。获得光标的位置或者选区的矩形边框可以使用
var rect = range.getBoundingClientRect();
rect对象包括的属性包括矩形的top、left、right、bottom坐标。 如果选取是collapsed的话,这四个属性就可以用来计算光标的位置了。
如果对选择区域内的元素进行了修改,比如添加新元素、改变元素类型等等, 那么原来的选区会失效。因此一个比较有用的技巧就是在修改元素之前,先保存选区 Range, 待修改完成后再恢复。
一个完整的例子如下:
var selection = window.getSelection();
var range = selection.getRangeAt(0);
// 保存所有 Range 的属性
var startContainer = range.startContainer;
var startOffset = range.startOffset;
var endContainer = range.endContainer;
var endOffset = range.endOffset;
// 进行元素修改操作
// ......
// 构造新的 Range
var newRange = document.createRange(); // 注意,此处必须创建一个新的选区,在原来的 range 上修改无效
newRange.setStart(startContainer, startOffset);
newRange.setEnd(endContainer, endOffset);
// 恢复选区
selection.removeAllRanges();
selection.addRange(newRange);
需要注意的是,有些操作可能会自动修改选区,那么使用上面方法就不能达到恢复选区的目的了。 一个常用的技巧是为恢复选区添加一个延迟,也就是在上面将addRange调用放入setTimeout当中。
setTimeout(function(){
selection.addRange(newRange);
}, 50);
文本编辑器的一个很重要的功能就是修改内容的样式,比如将文字加粗、倾斜、加下划线等。 还包括将段落修改为标题、块引用等。一个比较直观的方法是按照上述介绍的保存和恢复选区的方法, 按照需求修改元素添加样式即可。但是这种方法其实细想起来比较复杂,尤其是段落中, 存在混杂多种样式,以至于存在样式可以嵌套的情况(比如一段即是加粗又是倾斜的文字), 维护节点关系和清理空白节点会很繁琐。
好在浏览器为我们提供了一个方便的接口来实现这样的功能。那就是document.execCommand, 这个接口将各种操作抽象成命令的形式。下面展示了实现一些基本功能的方法。
document.execCommand('bold'); // 加粗所选
document.execCommand('italic'); // 倾斜所选
document.execCommand('underline'); // 下划线所选
document.execCommand('createLink', true, 'http://io-meter.com'); // 为所选文字加链接
document.execCommand('unlink'); // 取消链接
document.execCommand('formatBlock', true, 'h1'); // 将光标所在段落修改为一级标题
document.execCommand('formatBlock', true, 'p'); // 将光标所在块修改为段落
除此之外,浏览器还提供了一些编辑命令,如copy、cut和paste等。 完整的命令列表可以参见这个文档
需要注意的是,各种浏览器对这些命令的支持也有些不同,因此需要格外注意。 了解了这些命令,就具备实现编辑器中修改样式等功能的基本知识了。
在此还需要说一点题外话,在以往要侦听用户对文本的修改,一般是绑定keydown事件, 此外考虑到用户还可能选取并拖拽来改变内容,可能还要添加mouseup事件, 这种方法是低效且繁琐的,还对元素样式的修改无能为力。
还好,现代浏览器提供了一种新的机制用来检测内容的修改,那就是 Mutation Observer 机制。关于这方面,有一篇很好的文章值得阅读: Detect, Undo And Redo DOM Changes With Mutation Observers。
OK,了解了这些知识,实现一个简单的 Web 文本编辑器是不是也显得不那么难了呢? 虽然在这些 API 上不同浏览器还有些差异,但是已经被广泛应用在实现在线文本编辑功能了。
其实如果只专注于现代浏览器,那么实现如同 Medium 那样漂亮又实用的编辑和写作工具也不是非常困难的事!
newDate:function(d){ //把当地时间转换成北京时间 +8,
d=new Date(d);
d=d.getTime()-this.getBeijingTime()*60000
return new Date(d);
},
getBeijingTime:function(){
return this.getTimeOffset(8);
},
getTimeOffset:function(v){ //获取当地和指定时区的偏移,单位分钟
return new Date().getTimezoneOffset()+v*60
}
function runTransOne(obj,scalex){
$(obj).css('transform','scaleX('+scalex+')')
}
function tansTran(obj,bl,callback){
var scalex=bl||0,timeout;
timeout=setInterval(function(){
if(!bl){
scalex+=0.1;
if(scalex>1){
scalex=1;
clearInterval(timeout);
callback&& callback();
}
}else{
scalex-=0.1;
if(scalex<0){
scalex=0;
clearInterval(timeout);
callback&& callback();
}
}
runTransOne(obj,scalex)
},20);
}
function transDiv(obj,callback){
var tansObj=$(obj);
tansTran(tansObj,1,function(){
var obj=tansObj.next();
obj.show();
tansTran(obj,0,callback);
})
}
function transDiv2(obj,callback){
var tansObj=$(obj);
tansTran(tansObj,1,function(){
var obj=tansObj.prev();
tansTran(obj,0,callback);
})
}
var changed=0;
function startTransDiv(){
if(!changed){
$('.course_face').each(function(){
if($(this)[0].getBoundingClientRect().top>0){
changed=1;
var obj=$(this)
transDiv(obj,function(){
setTimeout(function(){transDiv2(obj.next())},1500);
});
}
})
}
}
$(function () {
var timeouts;
$(window).scroll(function(){
clearTimeout(timeouts);
timeouts=setTimeout(startTransDiv,100);
});
$('.course_face').on('mouseenter',function(){
transDiv($(this));
});
$('.course_back').on('mouseleave',function(){
transDiv2($(this));
});
});
html结构
<div class="course_face">
<strong class="course_score">445~550</strong>
<img class="course_pic " src="http://i2.c.hjfile.cn/lesson/intro/201312/e14987fa-64c0-4665-85a3-5f9eba858c4f.png" alt="">
</div>
<div class="course_back">aaaaaaaaaaa</div>
负责前端团队的管理及与其他团队的协调工作,提升团队成员能力和整体效率; 带领团队完成研发工具及平台前端部分的设计、研发和维护; 带领团队进行前端领域前沿技术研究及新技术调研,保证团队的技术领先 负责前端开发规范制定、功能模块化设计、公共组件搭建等工作,并组织培训。
实现了当前页面的,也作用在动态的更新和加载的图片。
修改需要懒加载的图片的属性,增加一个名称为lazyload的class
并把图片的src设置成通用的loading图片或者空白占位符图片
把图片的真实地址,写入图片的data-img中
经过以上三部,图片的html代码变成如下格式:
//示例
<img src="loading.gif" data-img="a.jpg" class="lazyload">
引入lazyload.js
增加如下代码
$.imgLazyLoad()
经过转换后,返回的对象是和location基本一致的结构;
$.extend($,{
parseURL:function(url) {
var a = document.createElement('a');
a.href = url;
return {
source: url,
protocol: a.protocol.replace(':',''),
host: a.hostname,
port: a.port,
query: a.search,
params: (function(){
var ret = {},
seg = a.search.replace(/^\?/,'').split('&'),
len = seg.length, i = 0, s;
for (;i<len;i++) {
if (!seg[i]) { continue; }
s = seg[i].split('=');
ret[s[0]] = s[1];
}
return ret;
})(),
file: (a.pathname.match(/\/([^\/?#]+)$/i) || [,''])[1],
hash: a.hash.replace('#',''),
path: a.pathname.replace(/^([^\/])/,'/$1'),
relative: (a.href.match(/tps?:\/\/[^\/]+(.+)/) || [,''])[1],
segments: a.pathname.replace(/^\//,'').split('/')
};
}
});
var url=$.parseURL('http://aa.bb.com:99/cc/dd.html')
console.log(url)
输出
file: "dd.html"
hash: ""
host: "aa.bb.com"
params: Object
path: "/cc/dd.html"
port: "99"
protocol: "http"
query: ""
relative: "/cc/dd.html"
segments: Array[2]
source: "http://aa.bb.com:99/cc/dd.html"
有一个弹窗,存在一个从上到下的动画处理。采用的是css3 动画,代码如下:
.slide_css_animate{
-webkit-transition:all .2s ease;
transition:all .2s ease;
}
.slideDown_up{margin-top:-1000px}
.slideDown{margin-top:0}
弹窗默认是slideDown_up的样式,也就是页面默认是在屏幕外的,但激活弹窗的时候,样式变为了slideDown,也就是在页面的顶部。中间由于有css3动画,所以会有一个从上到下的过程
但在华为手机上没有任何效果,也看不到弹窗的内容。
试着改变样式,采用transform:translate3d来呈现,也一样的没有效果;
采用JS实时监测弹窗的margin-top属性,发现该值还是保持-1000px;采用延时setTimeout,去重置margin-top:0后,弹窗还是保持不可见
这是JS监测margin-top属性,发现该值已经是0;
回过头来,把slideDown_up直接修改为0;发现弹窗是正常显示的。
就这样浆糊调试了3个小时后,突然灵光一现,试着去掉了css3的动画,也就是弹窗去掉transition:all .2s ease;后,发现问题竟然就这么解决了。
$.fn.extend({
placeholder:function () {
if ("placeholder" in document.createElement("input")) {
return this //如果原生支持placeholder属性,则返回对象本身
} else {
return this.each(function () {
var _this = $(this);
if(_this.attr('type')!='password'){
_this.val(_this.attr("placeholder")).focus(function () {
if (_this.val() === _this.attr("placeholder")) {
_this.val("")
}
}).blur(function () {
if (_this.val().length === 0) {
_this.val(_this.attr("placeholder"))
}
});
}else{
var newObj=$('<input type="text" value="'+_this.attr('placeholder')+'" autocomplete="off" class="'+_this[0].className+'" />');
_this.hide().after(newObj);
newObj.focus(function(){
newObj.hide();
_this.show().focus();
});
_this.blur(function(){
if(_this.val() == '') {
newObj.show();
_this.hide();
}
});
}
})
}
}
});
Gzip压缩项目文件大小
CDN加速,加快资源的访问速度
分布式静态服务器请求静态文件,设置浏览器缓存
保证请求资源尽量小
减少文件数量,减少文件请求
避免使用iframe,阻塞父级页面onload
精简DOM结构
初始首屏之外的图片资源需延迟加载
图片使用CSS Sprites 或 DATA URL
尽量不使用图片,如果可以使用css3实现
显示设置图片宽高,避免页面repaint
图片压缩,尽量使用JPGE,PNG8,如果图片超过50k,考虑叫设计进行优化
针对不同的设备,用media query调用不同size的图片
根据不同的网络状况,调用不同size的图片
合并css文件
使用高性能css选择器
使用css3代理js动画处理,使用translate3d开启GPU动画加速
禁止使用@import引入css文件
避免在低端机上使用大量CSS3渐变阴影效果,可考虑降级效果来提升流畅度
合并压缩js文件
单页面应用(SPA)考虑延迟加载非首屏业务模块
不可见Tab页延迟加载
避免使用大型类库
基础类库推荐使用zepto
尽可能使用事件代理,避免批量绑定事件
编写高性能javascript
使用localStorage进行数据缓存
1.避免使用click事件,避免 iOS 300+ms 点击延时问题
2.避免触发页面重绘restyle,repaint,relayout的操作,注意DOM元素的循环操作
3.小心连续触发的事件scroll/resize/touchmove,避免高频繁触发执行
使用阿里测,YSlow做性能检测
/*
* 识别电话号码,可以是多种格式的:
* 1,区号+号码,可以没有“-”
* 2,号码(7-8位)
* 3,号码-分机号,中间必须有“-”
* 4,区号+号码+分机号
* 5,多个电话号码,每个可以是以上4中之一,可以指定分隔符,默认为“,”分割。
* */
isPhone=function(v,split){
split=split||',';
v=v.split(split);
for(var i= 0,len= v.length;i<len;i++){
if(! /^(0\d{2,3})*-?[1-9]\d{6,7}(-\d{1,8})*$/.test(v[i])){
return false;
}
}
return true;
}
$.extend({
uploadFile:function(options){
var id='iframe'+(new Date()).getTime(),form,iframe,callback="fileUploadCallback"+id
window[callback]=function(d){
form.remove();
iframe.remove();
options.success(d);
}
options.url+=(options.url.indexOf('?')==-1?'?':'&')+'newTimes='+id+'callBack='+callback
alert(options.url)
form=$('<form style="width:0px;height:0px;overflow:hidden" action="'+options.url+'" method="POST" enctype="multipart/form-data" target="'+id+'"></form>'),
iframe=$('<iframe id="'+id+'" name="'+id+'" src="" style="position:absolute; top:-9999px; left:-9999px"></iframe>');
form.html($(options.fileobj).clone());
$('body').append(form).append(iframe)
form.submit();
}
});
基于浏览器的特性检测大家应该已经很熟悉了,特别是modernizr.js推出来之后。其实w3c也出了规范,可以基于css来做一些特性检测,也就是@supports,这个特性已经有两年多了,之前浏览器支持度不够,现在webkit nightly也开始支持了,也就是说safari 9会支持到,这样的话,Blink+webKit+gecko都支持了,只剩下IE浏览器不支持了,嗯,这样更方便对IE差异化处理了。
但是@supports并不能完全替代JS的特性检测方法,它只支持对CSS属性的检测,其它HTML5 api以及webP等技术的检测还是需要JS来完成。
@supports就像media query一样简单:
@supports(prop:value) {
/* more styles */
}
@supports也允许你用各种复杂的组合来进行特性检测。
@supports (display: flex) {
div { display: flex; }
}
你可以用这种方法来检测各种基本的CSS属性。
就像这样:
@supports not (display: flex) {
div { float: left; } /* alternative styles */
}
个人感觉有些鸡肋了,支持@supports的浏览器会不支持各种CSS属性么?不过万事没有绝对,这种情况以后还是可能会有。嗯,私有前缀的话可以试试。
@supports (display: -webkit-flex) or
(display: -moz-flex) or
(display: flex) {
/* use styles here */
}
/* and */
@supports (display: flex) and (-webkit-appearance: caret) {
/* something crazy here */
}
你也可以将or和and语句放在一起混用。
@supports ((display: -webkit-flex) or
(display: -moz-flex) or
(display: flex)) and (-webkit-appearance: caret) {
/* use styles here */
}
或者更复杂的:
@supports ( not ((text-align-last:justify) or (-moz-text-align-last:justify) ){
… /* specific CSS applied to simulate text-align-last:justify */
}
同时也可以用javascript来做类似的检测,方法也很简单:
boolValue = CSS.supports(propertyName, value);
boolValue = CSS.supports(supportCondition);
两种方法都可以,会返回一个bool值。比如:
result = CSS.supports("text-decoration-style", "blink");
result = CSS.supports("display", "flex");
result = CSS.supports("( transform-origin: 5% 5% )");
result = CSS.supports("( transform-style: preserve ) or ( -moz-transform-style: preserve ) or
( -o-transform-style: preserve ) or ( -webkit-transform-style: preserve )" );
最大的用途是做css特性判断的时候,不用再在js(或者传统的js方法)了,用过modernizr.js的同学可能会印象很深刻,modernizr会在html标签上加上各种各样的class来区分,其它js方法也是类似的实现思路。现在可以直接用@supports来区分或者做浏览器差异化了。
浏览器支持
chrome 28+
opera 12.1+
firefox22+
safari 9+
IE——可能IE12会支持
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>Jeremy - DragDrop Test !</title>
<meta name="keywords" content="Javascript自由拖拽类" />
<script type="text/javascript" src="jquery-1.9.1.js"></script>
<script type="text/javascript">
(function($,undefined){
$.fn.dragDrop = function(options,callback){
var defaults ={
focusElement:null, //点击哪个元素开始拖动,可为空。不为空时,需要为被拖动元素的子元素。
dir:'all', //拖动方向:['all','vertical','horizontal']
dirArea:null, //限制在哪个区域拖动,以数组形式提供[minX,maxX,minY,maxY]
dirMax:200 //单向拖动的最大值,单位PX
};
var opts = $.extend({},defaults,options),
bDraging = false,
moveElement = $(this),
ew=moveElement.outerWidth(),
eh=moveElement.outerHeight();
//点击哪个元素,以触发移动。
//该元素需要是被移动元素的子元素(比如标题等)
var focusElement = opts.focusElement ? $(opts.focusElement,moveElement) : moveElement ;
if(!focusElement || focusElement.length<=0){
alert('focusElement is not found! the element must be a child of '+this.id);
return false;
}
// statX|Y : 初始时,鼠标与被移动元素原点的距离
// moveX|Y : 移动时,被移动元素定位位置 (新鼠标位置与statX|Y的差值)
var dragParams = {statX:0,statY:0,moveX:0,moveY:0};
if(opts.dir=="all"){
moveElement.css({'position':'absolute','left':0,'top':0});
}else{
moveElement.css({'position':'absolute','left':-ew/2,'top':-eh/2});
}
focusElement.bind('mousedown',function(e){
bDraging = true;
//改变鼠标形状
moveElement.css({'cursor':'move'});
//捕获事件。(该用法,还有个好处,就是防止移动太快导致鼠标跑出被移动元素之外)
if(moveElement.get(0).setCapture){
moveElement.get(0).setCapture();
}
dragParams.statX = e.pageX - moveElement.position().left;
dragParams.statY = e.pageY - moveElement.position().top;
});
focusElement.bind('mousemove',function(e){
if(bDraging){
dragParams.moveX = e.pageX - dragParams.statX;
dragParams.moveY = e.pageY - dragParams.statY;
//dirArea格式: [左上角x,左上角Y,左下角x,左下角Y]
if(opts.dir=='all'){
if(opts.dirArea){
if(dragParams.moveX<opts.dirArea[0]){
dragParams.moveX=opts.dirArea[0]
}
if(dragParams.moveX>opts.dirArea[2]-ew){
dragParams.moveX=opts.dirArea[2]-ew
}
if(dragParams.moveY<opts.dirArea[1]){
dragParams.moveY=opts.dirArea[1]
}
if(dragParams.moveY>opts.dirArea[3]-eh){
dragParams.moveY=opts.dirArea[3]-eh
}
}
}else if (opts.dir=='vertical'){
if(dragParams.moveY<-eh/2){
dragParams.moveY=-eh/2;
}
if(dragParams.moveY>opts.dirMax-eh/2){
dragParams.moveY=opts.dirMax-eh/2;
}
}else{
if(dragParams.moveX<-ew/2){
dragParams.moveX=-ew/2;
}
if(dragParams.moveX>opts.dirMax-ew/2){
dragParams.moveX=opts.dirMax-ew/2;
}
}
if(opts.dir=='all'){
moveElement.css({'left':dragParams.moveX,'top':dragParams.moveY});
}else if (opts.dir=='vertical'){
moveElement.css({'top':dragParams.moveY});
}else if(opts.dir=='horizontal'){
moveElement.css({'left':dragParams.moveX});
}
if(typeof(callback)==='function'){
if(opts.dir=='all'){
callback(dragParams);
}else if(opts.dir=='vertical'){
callback(dragParams.moveY+eh/2);
}else{
callback(dragParams.moveX+ew/2);
}
}
}
});
focusElement.bind('mouseup',function(e){
bDraging=false;
moveElement.css({'cursor':'default'});
if(moveElement.get(0).releaseCapture){
moveElement.get(0).releaseCapture();
}
});
return {
value:function(v){
if(typeof(v)==undefined){
if (opts.dir=='vertical'){
return dragParams.moveY+eh/2
}else if(opts.dir=='horizontal'){
return dragParams.moveX+ew/2
}else{
return {x:dragParams.moveX,y:dragParams.moveY}
}
}else{
if (opts.dir=='vertical'){
moveElement.css({'top':v-eh/2});
}else if(opts.dir=='horizontal'){
moveElement.css({'left':v-ew/2});
}else{
moveElement.css({'left':v.x,'top':v.y});
}
}
}
}
return this;
};
})(jQuery,'undefined');
// test
$(function(){
//限定区域,有回调函数。
var d1=$('#dragDiv1').dragDrop({dir:'vertical',dirMax:100},function(d){
$('#span1').text(d);
});
//默认设置
var d2=$('#dragDiv2').dragDrop({dir:'horizontal',dirMax:200},function(d){
$('#span2').text(d);
});
var d3=$('#dragDiv3').dragDrop({dirArea:[0,0,500,500]},function(d){
$('#span3').text(d.moveX+','+d.moveY);
});
d3.value({x:333,y:222})
});
</script>
</head>
<body>
<div id="dragContainer" style="position:relative;left:10px;top:10px;border:1px dashed blue;width:500px;height:500px;">
<div id="dragDiv3" style="background:#333;height:100px;width:200px;">
<h4></h4>
<p>sfsoi174-poih;skahf9q08r
234234
<br>
asfaspolfjlsdkafj';l</p>
</div>
<div id="dragDiv2" style="border:1px solid red;height:50px;width:50px;">
</div>
<div id="dragDiv1" style="background-color:blue;height:50px;width:50px;">
</div>
</div>
<div id="span1"></div>
<div id="span2"></div>
<div id="span3"></div>
</body>
</html>
/style=('|")*[\w-#:;()]+[^'">]/gm
(function(){
var t={};
t.language=navigator.language||navigator.userLanguage;
t.cookie=navigator.cookieEnabled;
t.java=navigator.javaEnabled();
function getPlugin(pname){
var plugins=navigator.plugins,
len=plugins.length;
if(plugins && len>0 ){
for (var l = 0; l < len; l++) {
if(plugins[l].name.indexOf(pname.name)>-1) return true
}
return false;
}else{
try{new ActiveXObject(pname.fullname);
return true;
}
catch(e){
return false
}
}
}
t.flash=getPlugin({name:'Flash',fullname:'ShockwaveFlash.ShockwaveFlash'});
t.screen={
ah:screen.availHeight,
aw:screen.availWidth,
cd:screen.colorDepth,
h:screen.height,
w:screen.width
}
function cookie(name, value, options){
if (typeof value != 'undefined') {
options = options || {};
if (value === null) {
value = '';
options.expires = -1;
}
var expires = '', date;
if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) {
if (typeof options.expires == 'number') {
date = new Date();
date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000));
} else {
date = options.expires;
}
expires = '; expires=' + date.toUTCString();
}
var path = '; path=' + (options.path ? options.path : '/');
var domain = options.domain ? '; domain=' + options.domain : '';
var secure = options.secure ? '; secure' : '';
document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join('');
} else {
var cookieValue = null;
if (document.cookie && document.cookie != '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = $.trim(cookies[i]);
if (cookie.substring(0, name.length + 1) == (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
}
var ua=navigator.userAgent;
/* 操作系统监测 */
function getSystem(){
var ver, s,
system=navigator.platform,
win= system.indexOf('Win')== 0,
mac= system.indexOf('Mac')== 0,
x11= system.indexOf('X11')==0 || system.indexOf('Linux')== 0;
if(win){
if(/Win(?:dows\s)?([^do]{2}) (\d+\.\d+)/.test(ua)){
if(RegExp.$1=='NT'){
switch(RegExp.$2){
case '5.0':ver='2000';break;
case '5.1':ver='XP';break;
case '6.0':ver='Vista';break;
case '6.1':ver='7';break;
default :ver='NT';break;
}
}else if(RegExp.$1=='9x'){
ver='ME'
}else{
ver=RegExp.$1
}
}
s= 'Win '+ver;
}else if(mac){
s=system;
}else if(x11){
s=system
}
return s;
}
/* 移动监测 */
function getMobile(){
var m,f;
if(/Nokia([\w\s]+)/.test(ua)){
m='NOKIA '+RegExp.$1;
}
if(/Windows Phone (?:OS)?(\d+.\d+)/.test(ua)){
m='WinPhone '+RegExp.$1;
if(/NOKIA(?:;)? ([\w\s]+)/.test(ua)){
m='NOKIA '+RegExp.$1;
}
}
if(mac && ua.indexOf('Mobile')>-1){
if(/(\w+); CPU (?:iPhone )?OS (\d+_\d+)/.test(ua)){
m=RegExp.$1+' '+RegExp.$2.replace(/_/g,'.');
}
}
if(/Android/.test(ua)){
f=/([\w\s]+) Build/.test(ua);
m=RegExp.$1.replace(' ','');
}
if(/BlackBerry([^/]*)/i.text(ua)){
m='BlackBerry '+RegExp.$1
}
return m;
}
/* 浏览器及版本 */
function getBrowser(){
var b;
if(window.opera){
b='Opera '+window.opera.version();
}else if(/AppleWebKit\/(\S+)/.test(ua)){
b='Webkit '+RegExp.$1;
if(/Chrome\/(\S+)/.test(ua)){
b='Chrome '+RegExp.$1;
}else if(/Version\/(\S+)/.test(ua)){
b='Safari '+RegExp.$1;
}
}else if(/KHTML\/(\S+)/.test(ua) || /Konqueror\/([^;]+)/.test(ua)){
b='Konqueror '+RegExp.$1;
}else if(/rv:(\S+) Gecko\/\d{8}/.test(ua)){
b='Gecko '+RegExp.$1;
if(/Firefox\/(\S+)/.test(ua)){
b='Firefox '+RegExp.$1
}
}else if(/MSIE ([^;]+)/.test(ua)){
b='IE '+RegExp.$1;
}
return b;
}
})
ontouchmove="event.preventDefault()" //锁定viewport,任何屏幕操作不移动用户界面(弹出键盘除外)。
当前点击元素样式:
-webkit-tap-highlight-color: 颜色
html, body, form, fieldset, p, div, h1, h2, h3, h4, h5, h6 {-webkit-text-size-adjust:none;}
居中是移动端跟pc端共同的噩梦。这里有两种兼容性比较好的新方案。
.box{ text-align:center; display:table-cell; vertical-align:middle; }
.box{ display:-webkit-box; -webkit-box-pack: center; -webkit-box-align: center; text-align:center; }
这其实是浏览器自己的处理。因为浏览器会针对此类型 input 增加 datepicker 模块。
对 input type date 使用 placeholder 的目的是为了让用户更准确的输入日期格式,iOS 上会有 datepicker 不会显示 placeholder 文字,但是为了统一表单外观,往往需要显示。Android 部分机型没有 datepicker 也不会显示 placeholder 文字。
Safari 不支持 datepicker,placeholder 正常显示。
Firefox 不支持 datepicker,placeholder 正常显示。
Chrome 支持 datepicker,显示 年、月、日 格式,忽略 placeholder。
iPhone5 iOS7 有 datepicker 功能,但是不显示 placeholder。
Andorid 4.0.4 无 datepicker 功能,不显示 placeholder
因为text是支持placeholder的。因此当用户focus的时候自动把type类型改变为date,这样既有placeholder也有datepicker了
两个方法:使用css3动画的时尽量利用3D加速,从而使得动画变得流畅。动画过程中的动画闪白可以通过 backface-visibility 隐藏。
-webkit-transform-style: preserve-3d;
/*设置内嵌的元素在 3D 空间如何呈现:保留 3D*/
-webkit-backface-visibility: hidden;
/*(设置进行转换的元素的背面在面对用户时是否可见:隐藏)*/
document.implementation.hasFeature("http:// www.w3.org/TR/SVG11/feature#Image", "1.1")
IE Pseudo-elements
input:-ms-clear{display:none;}
如果你关闭自动识别后 ,又希望某些电话号码能够链接到 iPhone 的拨号功能 ,那么可以通过这样来声明电话链接 ,
<a href="tel:12345654321">打电话给我</a>
<a href="sms:12345654321">发短信</a>
或用于单元格:
<td onclick="location.href='tel:122'">
要关闭这两项功能,可以通过autocapitalize 与autocorrect 这两个选项:
<input type="text" autocapitalize="off" autocorrect="off" />
<meta content="email=no" name="format-detection" />
-webkit-touch-callout:none
-webkit-user-select:none
input::-webkit-input-speech-button {display: none}
今天测试同事发现的一个比较有意思的玩意,手机QQ的聊天输入框,那边有个图片上传的功能,发现刚点击,图片还没有上传呢,就出现了,可以这个体验非常不错,初步怀疑是近期上传过的历史记录;
为了证实自己的想法,我马上拿起我自己的手机QQ,一看我的手机没有这个功能,发现提示,原来是要开启手机QQ访问手机相册的权限;
好吧,马上设置,给手Q权限访问相册,发现聊天上传图片按钮下,出现了手机中的所有相册的图片缩略图列表;看上去不错,滑动体验也很好。
结合WEBAPP可以使用本地缓存来存储用户的交互数据和行为,虽然做不到直接访问用户手机相册的功能,但完全有能力可以保存用户上传过的图片。
鉴于此,是否可以采用简单的方法来存储用户数据,本实现类似的效果呢,我看完全是没有问题的,这样的体验也可以有效提升,特别是结合用户的图片使用频率以及时间间隔做一个简单排序。
所有content都支持是html文本
$.popWindow({
content:'window1',
type:2
})
$.popWindow({
title:'title',
content:'window1',
type:2
})
$.popWindow({
content:'window1',
type:2,
callback:function(){
alert(0)
}
})
$.popWindow({
content:'window1',
type:2,
closeBtn:true,
callback:function(){
alert(0)
}
})
$.popWindow({
content:'window1',
type:2,
yes:'aaaaaa',
closeBtn:true,
callback:function(){
alert(0)
}
})
$.popWindow({
content:'window1',
type:2,
yes:'aaaaaa',
tapMask:true,
closeBtn:true,
callback:function(){
alert(0)
}
})
和alert框的主要区别是,confim框有2个按钮,参数分别为:yes和no,默认yes=确认,no=取消
和alert一样,可以定制按钮的文案
具体如下:
$.popWindow({
content:'window1',
type:2,
yes:'确认'
no:'取消'
callback:function(bl){
if(bl){
alert(‘确认’)
}else{
alert('取消')
}
}
})
这个类型的弹窗,确认和取消按钮分别在左右上角
而接收用户输入交互发生在弹窗的内容区域,可以自己自定义content内容
具体如下:
$.popWindow({
content:'请输入....<textarea placeholder="请输入..."></textarea>',
yes:'确认',
no:'取消',
callback:function(bl){
if(bl){
}
}
})
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.