前言
- 由于工作需要,需要做一版在手机上查看的
H5
的广告页。广告页面基本都是一块内容占满一屏,然后上滑下滑就翻页,所以需要一个这样的翻页功能。
代码实现
/**
* 一个放置多个满屏页面的容器,支持上下滑动
*/
// ScrollBox.jsx
import React, { Component } from 'react';
import './ScrollBox.less';
class ScrollBox extends Component {
state = {
pageIndex: 0, // 当前在第几屏,从0开始算
}
startY = 0; // touch的起始Y坐标
handleTouchMove = (e) => {
const y = e.changedTouches[0].clientY;
const deltaY = y - this.startY;
const height = this.root.offsetHeight;
const len = this.props.list.length;
const pageIndex = this.state.pageIndex;
if(pageIndex === 0 && deltaY > 0) {
return;
}
if(pageIndex === len - 1 && deltaY < 0) {
return;
}
this.scrollBox.style.top = `${-(height * pageIndex - deltaY)}px`;
}
handleTouchStart = (e) => {
this.startY = e.changedTouches[0].clientY;
}
handleTouchEnd = (e) => {
const { list = [] } = this.props;
let { pageIndex } = this.state;
const y = e.changedTouches[0].clientY;
const deltaY = y - this.startY;
const len = list.length;
if(deltaY > 50) {
if(pageIndex > 0) {
pageIndex -= 1;
}
} else if(deltaY < -50) {
if(pageIndex < len - 1) {
pageIndex += 1;
}
}
this.setState({
pageIndex
}, () => {
this.anmateFunc();
})
}
anmateFunc = () => {
const currentStr = this.scrollBox.style.top;
let current = 0;
if(currentStr) {
current = Number(currentStr.slice(0, currentStr.length - 2));
}
const height = this.root.offsetHeight;
const target = -(height * this.state.pageIndex);
const delta = target - current;
const part = delta / 20;
const moveOnce = (i) => {
this.scrollBox.style.top = `${current + (i * part)}px`;
if(++i < 21) {
((index) => {
setTimeout(() => {
moveOnce(index);
}, 10);
})(i)
}
}
moveOnce(1);
}
componentDidMount() {
this.root = document.getElementById('root');
this.root.addEventListener('touchstart', this.handleTouchStart);
this.root.addEventListener('touchend', this.handleTouchEnd);
this.root.addEventListener('touchmove', this.handleTouchMove);
}
componentWillUnmount() {
this.root.removeEventListener('touchstart', this.handleTouchStart);
this.root.removeEventListener('touchend', this.handleTouchEnd);
this.root.removeEventListener('touchmove', this.handleTouchMove);
}
render() {
const { list = [] } = this.props;
const { pageIndex } = this.state;
return (
<div
className="ScrollBox"
ref={scrollBox => this.scrollBox = scrollBox}
>
{
list.map((child, index) => {
return (
<div className={`page ${pageIndex === index ? 'active' : ''}`} style={{ background: colors[index] }} key={`page-${index}`}>
<div style={{ fontSize: '1rem' }} className={`animate ${index % 2 === 0 ? 'animate-slideLeft' : 'animate-slideRight'}`}>
{
child
}
</div>
</div>
)
})
}
</div>
)
}
}
const colors = ['red', 'orange', 'yellow', 'green'];
export default ScrollBox;
// ScrollBar.less
.ScrollBox {
position: absolute;
top: 0;
left: 0;
.page {
width: 100vw;
height: 100vh;
}
// 当页面滑到可视区域时,可以添加一个active的类,方便做动画的管理,只有当页面出现到可视区域才加上动画效果,否则就不加。因为广告页大多是需要动画效果的
.active {
.animate {
animation-timing-function: ease-in-out;
animation-duration: 0.6s;
animation-iteration-count: 1;
&-slideLeft {
animation-name: slideLeft;
}
&-slideRight {
animation-name: slideRight;
}
@keyframes slideLeft {
0% {
transform: translateX(100%);
}
100% {
transform: translateX(0);
}
}
@keyframes slideRight {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(0);
}
}
}
}
}
// 使用
<ScrollBox
list={['page1', 'page2', 'page3', 'page4']}
/>
小结