移动web:翻页场景动画

  在移动web,特别是在微信中,经常看到一种翻页动画效果,也称为场景动画。

  一页一页的翻过,像在看书,每页的内容以各种"炫酷"的效果出现在你的眼里,配上一首动听的音乐,你有没有喜欢上呢。

  这里没有音乐,没有炫酷的出场,只有实实在在的翻页。

  先看看效果(如果不能查看 复制下面的代码保存在本地查看 或者在移动设备查看本页面)

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"/>
<meta name="format-detection"content="telephone=no" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<title>翻页场景动画</title>
<link rel="stylesheet" href="http://files.cnblogs.com/files/eyeear/slidePage-demo.css" />
</head>
<body>

<div id="slidePageBox" class="slide-box">
<section class="slide-page page1">
<div class="div1"></div>
<div class="div2"></div>
</section>
<section class="slide-page page2">
<div class="div1"></div>
<div class="div2"></div>
<div class="div3">
<p>一生就这么一次,</p>
<p>谈一场以结婚为目的的恋爱吧。</p>
<p>不再因为任性而不肯低头,</p>
<p>不再因为固执而轻言分手。</p>
<p>最后地坚信一次,一直走,就可以到白头。</p>
<p>惟愿这一生,执子之手,与子偕老。</p>
<p>你敢天长,我就敢地久。</p>
</div>
</section>
<section class="slide-page page3">
<div class="div1"></div>
<div class="div2">
<p>深思熟虑之后我们决定</p>
<p>为爱情做出重要的延续</p>
</div>
<div class="div3">邀请您一起见证</div>
</section>
<section class="slide-page page4">
<div class="div1"></div>
</section>
</div>
<section id="arrow"><img src="http://i11.tietuku.com/c0bd1d55cc05450b.png"></section>

<script type="text/javascript" src="http://files.cnblogs.com/files/eyeear/slidePage.min.js" ></script>
<script type="text/javascript">
//use slidePage
var arrow = document.getElementById('arrow'),
slide = new slidePage({
el: '#slidePageBox',
// locked: true,
// index: 2,
navShow: true,
// auto: true,
// delay: 8000,
effect: 'scale',
duration: 400,
// minScale: 0.8,
end: function() {
if(slide.index == slide.length - 1){
arrow.style.display = 'none';
}else{
arrow.style.display = 'block';
}
}
});
(function(doc, win) {
var docEl = doc.documentElement,
resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize',
recalc = function() {
var clientWidth = docEl.clientWidth;
if (!clientWidth) return;
clientWidth > 640 && (clientWidth = 640);
docEl.style.fontSize = 20 * (clientWidth / 320) + 'px';
};
if (!doc.addEventListener) return;
win.addEventListener(resizeEvt, recalc, false);
doc.addEventListener('DOMContentLoaded', recalc, false);
})(document, window);
</script>
</body>
</html>

  先贴上代码,仅供参考

/**
* LBS slidePage 绝对定位方式 支持WP
* Date: 2014-11-20
* ===================================================
* opts.el 外围包裹容器/滑动事件对象(一个字符串的CSS选择器或者元素对象)
* opts.index 索引(默认0) 指定显示哪个索引的页
* opts.current 当前页添加的类名(默认'current')
* opts.navShow 是否需要导航指示 默认false不需要
* opts.navClass 导航指示容器的类名 方便设置样式 (默认'slide-nav')
* opts.auto 是否自动播放 默认false
* opts.delay 自动播放间隔时间 默认5000(单位毫秒) 自动播放时有效
* opts.locked 是否锁定头尾滑动 默认false 如果开启则不能使用自动播放
* opts.effect 动画效果(平移=translate 缩放=scale 重叠=overlap) 默认平移
* opts.duration 动画持续时间 默认300(单位毫秒)
* opts.minScale 动画效果为缩放时的最小缩放比率(0 ~ 1) 1为没有缩放效果 默认0.5
* opts.start 手指按下时 执行函数
* opts.move 手指移动中 执行函数
* opts.end 手指收起后 执行函数
* ===================================================
* this.box 包裹页的容器对象
* this.index 当前索引
* this.length 有多少页 最后一页的索引为 this.length-1
* this.play 调用自动播放的方法
* this.stop 清除自动播放的方法
* this.up 手动调用向上滑动翻页的方法 方便增加点击按钮时调用
* this.down 手动调用向下滑动翻页的方法
* ===================================================
**/
;(function() {
window.slidePage = function(opts) {
opts = opts || {};
if (opts.el === undefined) return;
this.box = typeof opts.el === 'string' ? document.querySelector(opts.el) : opts.el;
this.pages = this.box.children;
this.length = this.pages.length;
if (this.length < 1) return;
if (opts.index > this.length - 1) opts.index = 0; this.body = document.getElementsByTagName('body')[0];
this.nav = null;
this.navs = [];
this.navShow = !!opts.navShow || false;
this.navClass = opts.navClass || 'slide-nav'; this.index = this.oIndex = opts.index || 0;
this.current = opts.current || 'current';
this.locked = !!opts.locked || false;
this.auto = !!opts.auto || false;
this.auto && (this.delay = opts.delay || 5000);
this.effect = opts.effect || 'translate';
this.duration = opts.duration || 300;
this.minScale = opts.minScale || 0.5; this.start = opts.start || function() {};
this.move = opts.move || function() {};
this.end = opts.end || function() {}; this.timer = null;
this.animated = true;
this.touch = {};
this.point = ''; this.init();
};
slidePage.prototype = {
init: function() {
this.navShow && this.createNav();
this.initSet();
this.bind();
},
createNav: function() {
var li = null,
i = 0;
this.nav = document.createElement('ul');
for (; i < this.length; i++) {
li = document.createElement('li');
this.navs.push(li);
this.nav.appendChild(li);
}
this.nav.className = this.navClass;
this.body.appendChild(this.nav);
},
initSet: function() {
this.height = document.documentElement.clientHeight || document.body.clientHeight;
if (this.css(this.box, 'position') !== 'absolute' || this.css(this.box, 'position') !== 'relative') this.box.style.position = 'relative';
if (this.css(this.box, 'overflow') !== 'hidden') this.box.style.overflow = 'hidden';
this.box.style.height = this.height + 'px';
for (var i = 0; i < this.length; i++) {
if (this.css(this.pages[i], 'display') !== 'none') this.pages[i].style.display = 'none';
if (this.css(this.pages[i], 'position') !== 'absolute') this.pages[i].style.position = 'absolute';
this.pages[i].style.height = this.height + 'px';
}
if (this.navShow) {
this.nav.style.marginTop = -this.nav.offsetHeight / 2 + 'px';
this.navs[this.index].className = this.current;
}
this.pages[this.index].className += ' ' + this.current;
this.zIndex = parseInt(this.css(this.pages[this.index], 'zIndex') === 'auto' ? 1 : this.css(this.pages[this.index], zIndex)) + 10;
this.pages[this.index].style.display = 'block';
},
bind: function() {
var _this = this;
this.on(this.box, ['touchstart', 'pointerdown', 'mousedown'], function(e) {
_this.touchStart(e);
_this.auto && _this.stop();
});
this.on(this.box, ['touchmove', 'pointermove', 'mousemove'], function(e) {
_this.touchMove(e);
_this.auto && _this.stop();
});
this.on(this.box, ['touchend', 'touchcancel', 'pointerup', 'mouseup'], function(e) {
_this.touchEnd(e);
_this.auto && _this.play();
});
},
touchStart: function(e) {
this.point = e.type.indexOf('down') < 0 ? 'touch' : 'pointer';
if (this.point === 'pointer') {
this.touch.x = e.pageX;
this.touch.y = e.pageY;
}else if (this.point === 'touch') {
this.touch.x = e.touches[0].pageX;
this.touch.y = e.touches[0].pageY;
}
this.touch.disX = 0;
this.touch.disY = 0;
this.touch.fixed = '';
this.start && this.start();
},
touchMove: function(e) {
e.stopPropagation();
e.preventDefault();
if (this.point === '') return;
if (this.touch.fixed === 'left') return;
if (!this.animated) return;
if (this.point === 'pointer') {
this.touch.disX = e.pageX - this.touch.x;
this.touch.disY = e.pageY - this.touch.y;
}else if (this.point === 'touch') {
if (e.touches.length > 1) return;
this.touch.disX = e.touches[0].pageX - this.touch.x;
this.touch.disY = e.touches[0].pageY - this.touch.y;
}
if (this.touch.fixed === '') {
if (Math.abs(this.touch.disY) > Math.abs(this.touch.disX)) {
this.touch.fixed = 'up';
} else {
this.touch.fixed = 'left';
}
}
if (this.touch.fixed === 'up') {
if (this.effect === 'scale') {
this.scale = ((this.height - Math.abs(this.touch.disY)) / this.height).toFixed(3);
this.scale < this.minScale && (this.scale = this.minScale);
}
if (this.touch.disY > 0) {
if (this.locked && this.oIndex === 0) return;
this.dis = -this.height;
this.index = this.oIndex - 1;
this.index < 0 && (this.index = this.length - 1);
if (this.effect === 'scale') this.setOrigin(this.oIndex, 'center bottom');
} else {
if (this.locked && this.oIndex === this.length - 1) return;
this.dis = this.height;
this.index = this.oIndex + 1;
this.index > this.length - 1 && (this.index = 0);
if (this.effect === 'scale') this.setOrigin(this.oIndex, 'center top');
} if (this.nIndex !== undefined && this.nIndex !== this.index && this.nIndex !== this.oIndex) {
this.pages[this.nIndex].style.display = 'none';
this.pages[this.nIndex].style.zIndex = '';
this.pages[this.nIndex].style.webkitTransform = this.pages[this.nIndex].style.transform = '';
}
this.nIndex = this.index; this.pages[this.oIndex].style.zIndex = this.zIndex;
this.pages[this.index].style.zIndex = this.zIndex + 10;
this.setTransform(this.index, this.dis);
this.pages[this.index].style.display = 'block'; this.setTransform(this.index, this.touch.disY + this.dis);
if (this.effect === 'translate') this.setTransform(this.oIndex, this.touch.disY);
if (this.effect === 'scale') this.setScale(this.oIndex, this.scale); this.move && this.move();
}
},
touchEnd: function(e) {
this.point = '';
if (this.index === this.oIndex) return;
if (this.touch.fixed === 'up') {
var Y = Math.abs(this.touch.disY);
if ((this.animated && Y > 10) || Y > this.height / 2) {
this.slide();
} else {
this.goback();
}
this.end && this.end();
}
},
css: function(o, n) {
return getComputedStyle(o, null)[n];
},
on: function(el, types, handler) {
for (var i = 0, l = types.length; i < l; i++) el.addEventListener(types[i], handler, false);
},
setScale: function(index, v) {
this.setStyle(this.pages[index], 'transform', 'scale(' + v + ')');
},
setOrigin: function(index, dir) {
this.setStyle(this.pages[index], 'transform-origin', dir);
},
setTransform: function(index, v) {
this.setStyle(this.pages[index], 'transform', 'translate3d(0,' + v + 'px,0)');
},
setTransition: function(index, v) {
this.setStyle(this.pages[index], 'transition', 'all ' + v + 'ms');
},
setStyle: function(el, p, v) {
var prefix = ['o', 'moz', 'ms', 'webkit', ''],
i = 0,
l = prefix.length;
for (; i < l; i++) {
(function(i) {
var s = prefix[i] + '-' + p;
s = s.replace(/-\D/g, function(match) {
return match.charAt(1).toUpperCase();
});
el.style[s] = v;
}(i));
}
},
slide: function() {
var _this = this;
this.animated = false;
this.setTransition(this.index, this.duration);
this.setTransition(this.oIndex, this.duration);
this.setTransform(this.index, 0);
if (this.effect === 'translate') this.setTransform(this.oIndex, -this.dis);
if (this.effect === 'scale') this.setScale(this.oIndex, this.minScale);
setTimeout(function() {
if (_this.index !== _this.oIndex) _this.update();
_this.animated = true;
}, this.duration);
},
goback: function() {
var _this = this;
this.setTransition(this.index, 100);
this.setTransition(this.oIndex, 100);
this.setTransform(this.index, this.dis);
if (this.effect === 'translate') this.setTransform(this.oIndex, 0);
if (this.effect === 'scale') this.setScale(this.oIndex, 1);
setTimeout(function() {
_this.clear();
_this.pages[_this.index].style.display = 'none';
_this.index = _this.oIndex;
}, 100);
},
update: function() {
if (this.navShow) {
this.navs[this.index].className = this.current;
this.navs[this.oIndex].className = '';
}
this.pages[this.oIndex].style.display = 'none';
this.pages[this.index].className += ' ' + this.current;
this.pages[this.oIndex].className = this.pages[this.oIndex].className.replace(this.current, '').trim();
this.clear();
this.oIndex = this.index;
},
clear: function() {
this.pages[this.index].style.webkitTransition = this.pages[this.index].transition = '';
this.pages[this.oIndex].style.webkitTransition = this.pages[this.oIndex].transition = '';
this.pages[this.index].style.webkitTransform = this.pages[this.index].style.transform = '';
this.pages[this.oIndex].style.webkitTransform = this.pages[this.oIndex].style.transform = '';
this.pages[this.oIndex].style.zIndex = '';
this.pages[this.index].style.zIndex = '';
if (this.effect === 'scale') this.setOrigin(this.oIndex, '');
},
animate: function() {
var _this = this;
this.setTransform(this.index, this.dis);
this.pages[this.index].style.display = 'block';
this.pages[this.oIndex].style.zIndex = this.zIndex;
this.pages[this.index].style.zIndex = this.zIndex + 10;
this.setTransition(this.index, 0);
this.setTransition(this.oIndex, 0);
setTimeout(function() {
_this.slide();
}, 50);
},
up: function() {
this.dis = this.height;
this.index++;
this.index > this.length - 1 && (this.index = 0);
if (this.effect === 'scale') this.setOrigin(this.oIndex, 'center top');
this.animate();
},
down: function() {
this.dis = -this.height;
this.index--;
this.index < 0 && (this.index = this.length - 1);
if (this.effect === 'scale') this.setOrigin(this.oIndex, 'center bottom');
this.animate();
},
play: function() {
var _this = this;
if (this.locked) return;
this.timer = setInterval(function() {
_this.up();
}, this.delay);
},
stop: function() {
this.timer && clearInterval(this.timer);
this.timer = null;
}
};
}());

查看完整代码

  和以前写的 图片切换 有许多共同的地方,整个翻页和图片切换原理都是类似的。

  这个支持自动播放,三种翻页效果,限制头尾滑动等。

  翻页动画,就是在一个容器内滑动,在这个容器中每次只显示一页,每一页都有一些css3动画效果的元素出现。

  一个简单的HTML结构如下:

<div id="slidePageBox" class="slide-box">
<section class="slide-page page1">
<!-- 1 -->
</section>
<section class="slide-page page2">
<!-- 2 -->
</section>
<section class="slide-page page3">
<!-- 3 -->
</section>
<section class="slide-page page4">
<!-- 4 -->
</section>
<section class="slide-page page5">
<!-- 5 -->
</section>
</div>

  类名为slide-box的div标签就是容器,类名为slide-page的section标签就是每个要翻的页。

this.box = typeof opts.el === 'string' ? document.querySelector(opts.el) : opts.el;
this.pages = this.box.children;
this.length = this.pages.length;

  这里this.box就是容器,this.pages就是所有要翻的页。

this.height = document.documentElement.clientHeight;
this.box.style.height = this.height + 'px';
for (var i = 0; i < this.length; i++) {
//..
this.pages[i].style.height = this.height + 'px';
}

  获取浏览器窗口的高,并设置容器和页的高为这个值。这个翻页是上下滑动,JS就没有获取宽度值了。

  容器宽用css设置为width:100%,做个最大宽度限制为max-width:640px,这可根据实际情况设置。

   现在切换翻页,当手指在容器滑动时,这里为判断上下滑动,满足滑动一定距离,手指离开容器时,开始翻页。

  触摸事件:手指移入,手指移动,手指离开

  指针事件:指针按下,指针移动,指针收起

  鼠标事件: 鼠标按下,鼠标移动,鼠标收起

//..
bind: function() {
var _this = this;
this.on(this.box, ['touchstart', 'pointerdown', 'mousedown'], function(e) {
_this.touchStart(e);
//..
});
this.on(this.box, ['touchmove', 'pointermove', 'mousemove'], function(e) {
_this.touchMove(e);
//..
});
this.on(this.box, ['touchend', 'touchcancel', 'pointerup', 'mouseup'], function(e) {
_this.touchEnd(e);
//..
});
},
//..

  绑定了触摸事件,WP的指针事件,鼠标事件。

//..
touchStart: function(e) {
this.point = e.type.indexOf('down') < 0 ? 'touch' : 'pointer';
if (this.point === 'pointer') {
this.touch.x = e.pageX;
this.touch.y = e.pageY;
}else if (this.point === 'touch') {
this.touch.x = e.touches[0].pageX;
this.touch.y = e.touches[0].pageY;
}
//..
},
//..

  获取移入时的初始位置(触摸,指针,鼠标)

//..
touchMove: function(e) {
//..
if (this.touch.fixed === 'left') return;
//..
if (this.point === 'pointer') {
this.touch.disX = e.pageX - this.touch.x;
this.touch.disY = e.pageY - this.touch.y;
}else if (this.point === 'touch') {
//..
this.touch.disX = e.touches[0].pageX - this.touch.x;
this.touch.disY = e.touches[0].pageY - this.touch.y;
}
if (this.touch.fixed === '') {
if (Math.abs(this.touch.disY) > Math.abs(this.touch.disX)) {
this.touch.fixed = 'up';
} else {
this.touch.fixed = 'left';
}
}
if (this.touch.fixed === 'up') {
//..
}
},
//..

  移动时(触摸,指针,鼠标)获取移动了多少距离,根据距离判断向左右还是上下移动的,这里翻页只要上下移动。

//..
if (this.touch.fixed === 'up') {
if (this.effect === 'scale') {
this.scale = ((this.height - Math.abs(this.touch.disY)) / this.height).toFixed(3);
this.scale < this.minScale && (this.scale = this.minScale);
}
if (this.touch.disY > 0) {
if (this.locked && this.oIndex === 0) return;
this.dis = -this.height;
this.index = this.oIndex - 1;
this.index < 0 && (this.index = this.length - 1);
if (this.effect === 'scale') this.setOrigin(this.oIndex, 'center bottom');
} else {
if (this.locked && this.oIndex === this.length - 1) return;
this.dis = this.height;
this.index = this.oIndex + 1;
this.index > this.length - 1 && (this.index = 0);
if (this.effect === 'scale') this.setOrigin(this.oIndex, 'center top');
} if (this.nIndex !== undefined && this.nIndex !== this.index && this.nIndex !== this.oIndex) {
this.pages[this.nIndex].style.display = 'none';
this.pages[this.nIndex].style.zIndex = '';
this.pages[this.nIndex].style.webkitTransform = this.pages[this.nIndex].style.transform = '';
}
this.nIndex = this.index; this.pages[this.oIndex].style.zIndex = this.zIndex;
this.pages[this.index].style.zIndex = this.zIndex + 10;
this.setTransform(this.index, this.dis);
this.pages[this.index].style.display = 'block'; this.setTransform(this.index, this.touch.disY + this.dis);
if (this.effect === 'translate') this.setTransform(this.oIndex, this.touch.disY);
if (this.effect === 'scale') this.setScale(this.oIndex, this.scale); this.move && this.move();
}
//..

移动中

  移动中做了很多事情,是整个程序比较重要的部分。在上下移动时,当前显示的页跟着移动,向上移动时,下一页显示出来一部分,向下移动时,上一页显示一部分。

  如果锁定了头尾,第一页向下移动时不能移动,同样最后一页向上移动时不能移动。

opts.effect 动画效果(平移=translate 缩放=scale 重叠=overlap) 默认平移

  根据每种翻页效果做初始化设置(如果只需要简单的平移切换可以省去很多代码)。

//..
touchEnd: function(e) {
this.point = '';
if (this.index === this.oIndex) return;
if (this.touch.fixed === 'up') {
var Y = Math.abs(this.touch.disY);
if ((this.animated && Y > 10) || Y > this.height / 2) {
this.slide();
} else {
this.goback();
}
//..
}
},
//..

  离开时(触摸,指针,鼠标)根据条件执行滑动切换。

//..
slide: function() {
var _this = this;
this.animated = false;
this.setTransition(this.index, this.duration);
this.setTransition(this.oIndex, this.duration);
this.setTransform(this.index, 0);
if (this.effect === 'translate') this.setTransform(this.oIndex, -this.dis);
if (this.effect === 'scale') this.setScale(this.oIndex, this.minScale);
setTimeout(function() {
if (_this.index !== _this.oIndex) _this.update();
_this.animated = true;
}, this.duration);
},
//..

  滑动切换完成时会更新一些设置,比如为当前页增加当前标志类名。每页的css3动画元素也就可以根据这个类名执行动画效果。

//..
update: function() {
if (this.navShow) {
this.navs[this.index].className = this.current;
this.navs[this.oIndex].className = '';
}
this.pages[this.oIndex].style.display = 'none';
this.pages[this.index].className += ' ' + this.current;
this.pages[this.oIndex].className = this.pages[this.oIndex].className.replace(this.current, '').trim();
this.clear();
this.oIndex = this.index;
},
//..

  切换了页,执行了每页的动画,整个场景动画就差不多完成了。

   最后就是一些简单的了,自动播放什么的。

  好了,到此结束。

   ---------------- 补充 ------------------------

   为了兼容各大浏览器,css3动画属性前缀值得注意。

//..
setStyle: function(el, p, v) {
!this.cache[el] && (this.cache[el] = {});
!this.cache[el][p] && (this.cache[el][p] = this.prefix(p));
el.style[this.cache[el][p] || this.prefix(p)] = v;
},
prefix: function(p) {
var style = document.createElement('div').style;
if (p in style) return p;
var prefix = ['webkit', 'Moz', 'ms', 'O'],
i = 0,
l = prefix.length,
s = '';
for (; i < l; i++) {
s = prefix[i] + '-' + p;
s = s.replace(/-\D/g, function(match) {
return match.charAt(1).toUpperCase();
});
if (s in style) return s;
}
},
//..

  这里做了PC端查看兼容,注意IE的pointer移动事件会有个很有意思的表现,移动端的程序还是在移动设备中查看比较好。

 

上一篇:Manacher's Algorithm 马拉车算法


下一篇:博客已经转移到http://geekori.cn,本博客不再更新