1. 习惯所致,通常开始写代码之前写一点准备代码, 完全是为了减少代码量方便阅读
$ = function(sel, holder){
return [].slice.call( (holder||document).querySelectorAll(sel) );
};
$.on = function(dom, eventType, f){
[].concat(dom).forEach(function(d){
eventType.split(/W+/).forEach(function(type){
d.addEventListener(type, f ,false);
});
});
return $;
};
2. 第一步创建一个类似swiper的切换效果,当然我们只是操作纵向,在CSS3中使用 transform 位置变换 + 过渡效果,很容易实现,关键是需要在事件绑定时候进行计算。另外,如果所有页面都是100%宽100%高的话自然不需要考虑内部滚动事件,我们这里需要考虑更多的事情,所以最好使用额外的一个透明层做事件绑定,然后操作与真实的section内容。
container = $("#container")[0]
holder = document.createElement("div"),
holder.style.cssText = "position: absolute;width: 100%;height: 100%;left: 0;top: 0;z-index: 1;";
container.appendChild(holder);
3. 切换的方法我暂且定做 transform_set(dom/*当前section*/, i/*前移还是后移i个section*/); 大概就是酱紫了
var H = document.documentElement.clientHeight;
if( "undefined" === typeof i ){
i = dom | 0;
}else if( "number" != typeof dom ){
i = sections.indexOf(dom) + i;
}
i = Math.min( Math.max( 0, i ), sections.length - 1);
css_model = function(h,l){
return "-webkit-transform: translate3d("+(l|0)+"px, "+(h|0)+"px, 0); transform: translate3d("+(l|0)+"px, "+(h|0)+"px, 0);";
};
sections.forEach(function(section, index){
switch(index){
case i:
current_index = i;
section.style.cssText = css_model();
break;
case i+1:
section.style.cssText = css_model(H);
break;
case i-1:
section.style.cssText = css_model(-H);
break;
default:
section.style.cssText = css_model( (index > i) ? H : -H );
}
});
4. 注意事件绑定的时候需要判断方向,来确定切换是前移还是后移
// 事件
var startTy, curTy, endTy, autoStep;
$.on(holder, "touchstart", function(e){
var touch = e.touches[0];
startTy = curTy = touch.clientY;
}).on(holder, "touchend", function(e){
var touch = e.changedTouches[0], //只判断单点触控
endTy = touch.clientY;
transform_set(sections[current_index], startTy > endTy ? 1 : -1);
startTy = curTy = endTy = 0;
}).on(document, "keydown", function(e){ // key 和 mouse 事件只是为了方便PC浏览器
switch(e.keyCode){
case 32:
case 39:
case 40: transform_set(sections[current_index], 20); break;
case 37:
case 38: transform_set(sections[current_index], -20); break;
}
}).on(document, "mousewheel", function(e){
transform_set(sections[current_index], e.wheelDeltaY < 0 ? 20: -20);
});
这节先到这里,事实上已经完成了一个方便的swiper效果。 然并卵,复杂的内容还木有开始(scrollTop位置检测,内置元素的相对transform设置和参数设计,动画元素分组,图片loading,按钮元素模拟穿透等), 详情关注本系列文章。
在前文中,我们简单讲了一个移动端整页切换的效果处理,本文主要来识别滚屏动画的进度接口设置。
考虑到单页面内滚动高度可能随着不同屏幕分辨率有所不同,我们设计的接口只是提供一个滚动百分比的参数,这个参数的值可以从 0 ~ 1.
run(per/*滚动百分比*/,dir/*滚动方向*/) // 滚动方向不一定用得上,暂且留着。
我们将这个run假定注册在原生的dom元素上面,再加一个run的className来识别,那么相关代码就会如下:
$.on(holder, "touchstart", function(e){
var touch = e.touches[0];
startTy = curTy = touch.clientY;
}).on(holder, "touchmove", function(e){
var touch = e.changedTouches[0],
endTy = touch.clientY;
run( curTy - endTy, e);
curTy = endTy;
});
function run(dir, e){
e.stopPropagation();
e.preventDefault();
var _this = sections[current_index];
var st = _this.scrollTop,
ch = _this.clientHeight,
sh = _this.scrollHeight;
scrollTy = st;
st = _this.scrollTop = _this.scrollTop + dir;
// 这里还有判断跳切换页面的逻辑
$(".run", _this).forEach(function(r){ // 对当前section中的所有.run循环,判断是否存在run方法
if( typeof r.run === "function" ){
var per = _this.scrollTop == (sh-ch) ? 1 : _this.scrollTop / (sh-ch);
r.run(per, dir); // 存在这个方法就执行它, 把per带上。 per计算来自于当前滚动高度÷最大滚动高度
}
});
}
完事儿,我们需要达到的效果其实就是酱紫的:
document.getElementById('logo').run = function(per, dir){
var W = document.documentElement.clientWidth;
this.style.cssText =
"-webkit-transform: translate3d("+( (140+W) * per |0)+"px, "+(this.parentNode.scrollTop|0)+"px, 0); "
+"transform: translate3d("+((140+W) * per |0)+"px, "+(this.parentNode.scrollTop|0)+"px, 0);";
};
绑定一个run,就能按照规则执行滚动动画咯。
demo: http://cardistry.cn/hero/demo.html
建议用手机查看,或者使用chrome的移动端调试控制台。
查看源代码的时候你会发现如下代码:
.set = function(set,w,h,W,H){
set.begin({x:0,y:0,rotate: 0, scale:1}).then({
per: .3,
x: w-W,
y: 300,
rotate: 180,
scale: .5
}).then({
per: .5,
x: -100,
y: 200,
opacity: 1,
rotate: 360,
scale: 1
}).then({
per: .7,
x: -100,
y: 200,
opacity: 1,
rotate: 240,
scale: 1.5
}).then({
per: .8,
x: -100,
y: 200,
opacity:.2,
rotate: 120,
scale: 1
}).end({x:w-W,y:H-h,rotate:0, scale:1});
};
看起来很高大上的样子, 写动画好像更加方便了。这个其实就是基于上面run的一个线性封装,具体实现
前文提到,在一版页面内部滚动时候我们使用一个run方法传入参数per执行,期望接口使用者方便调用。
然而事实上, 还有一些个通用的执行过程可以进行抽离,例如我们期望通过如下方式书写动画:
.set = function(set,w,h,W,H){
set.begin({x:0,y:0,rotate: 0, scale:1}).then({
per: .3,
x: w-W,
y: 300,
rotate: 180,
scale: .5
}).then({
per: .5,
x: -100,
y: 200,
opacity: 1,
rotate: 360,
scale: 1
}).then({
per: .7,
x: -100,
y: 200,
opacity: 1,
rotate: 240,
scale: 1.5
}).then({
per: .8,
x: -100,
y: 200,
opacity:.2,
rotate: 120,
scale: 1
}).end({x:w-W,y:H-h,rotate:0, scale:1});
};
看起来像CSS3-animation实现过程呶。 建立在run方法已经可以被检测执行的基础上,我们可以想办法把这些数据构建一个run方法来:
1. 处理过程存储和参数部分:
var trace = [],
set = {
begin: function(d){d.per = d.per || 0;trace.push(d);return this;},
end: function(d){d.per = d.per || 1;trace.push(d);return this;},
then: function(d){trace.push(d);return this;}
},
w = r.offsetWidth,
h = r.offsetHeight,
W = document.documentElement.clientWidth,
H = document.documentElement.clientHeight;
2. 执行dom的set方法,将过程存储到trace数组中:
r.set(set,w,h,W,H);
3. 封装成为run方法(过程中依次循环数组,当属于某区间时候计算区间内属性的线性变换并且赋值)支持的属性有限,位置变换移动端不能使用left、top:
r.run = function(per, dir){
for (var i = 1; i < trace.length; i++) {
if( trace[i-1].per <= per && trace[i].per >= per ){
var prev = trace[i-1],
next = trace[i],
rate = (per - prev.per) / (next.per - prev.per),
_x = (prev.x+(next.x-prev.x)*rate) || 0,
_y = (prev.y+(next.y-prev.y)*rate) + sections[current_index].scrollTop || 0,
_o = (prev.opacity+(next.opacity-prev.opacity)*rate),
_s = (prev.scale+(next.scale-prev.scale)*rate),
_r = (prev.rotate+(next.rotate-prev.rotate)*rate),
_s = isNaN(_s) ? 1 : _s;
_r = isNaN(_r) ? 0 : _r;
var cssText =
"-webkit-transform: scale("+_s+") translate3d("+_x+"px, "+_y+"px, 0) rotate("+_r+"deg);"
+"transform: scale("+_s+") translate3d("+_x+"px, "+_y+"px, 0) rotate("+_r+"deg);"
+ ( isNaN(_o) ? "" : ("opacity: " + _o+ ";") );
this.style.cssText = cssText;
return ;
}
};
};