Banner.js
import React from 'react';
import PropTypes from 'prop-types';
import '../static/css/banner.css';
export default class Banner extends React.Component {
//=>设置属性的默认值和规则
static defaultProps = {
data: [],
interval: 3000,
step: 1,
speed: 300
};
static propTypes = {
data: PropTypes.array,
interval: PropTypes.number,
step: PropTypes.number,
speed: PropTypes.number
};
constructor(props) {
super(props);
//=>INIT STATE
let {step, speed} = this.props;
this.state = {
step,
speed
};
}
//=>数据的克隆
componentWillMount() {
let {data} = this.props,
cloneData = data.slice(0);
cloneData.push(data[0]);
cloneData.unshift(data[data.length - 1]);
this.cloneData = cloneData;//=>挂载到实例上供其它方法调用
}
//=>自动轮播
componentDidMount() {
//=>把定时器返回值挂载到实例上,方便后期清除:结束自动轮播
this.autoTimer = setInterval(this.autoMove, this.props.interval);
}
componentWillUpdate(nextProps, nextState) {
//=>边界判断:如果最新修改的STEP索引大于最大索引(说明此时已经是末尾了,不能在向后走
//了),我们让其立即回到(无动画)索引为1的位置
if (nextState.step > (this.cloneData.length - 1)) {
this.setState({
step: 1,
speed: 0
});
}
}
componentDidUpdate() {
//=>只有是从克隆的第一张立即切换到真实第一张后,我们才做如下处理:让其从当前第一张运动
//到第二张即可
let {step, speed} = this.state;
if (step === 1 && speed === 0) {
//=>为啥要设置定时器延迟:CSS3的TRASITION有一个问题(主栈执行的时候,短时间内
//遇到两次设置TRANSITION-DRRATION的代码,以最后一次设置的为主)
let delayTimer = setTimeout(() => {
clearTimeout(delayTimer);
this.setState({
step: step + 1,
speed: this.props.speed
});
}, 0);
}
}
render() {
let {data} = this.props,
{cloneData} = this;
if (data.length === 0) return '';
//=>控制WRAPPER的样式
let {step, speed} = this.state,
wrapperSty = {
width: cloneData.length * 1000 + 'px',
left: -step * 1000 + 'px',
transition: `left ${speed}ms linear 0ms`
};
return <section className='container'>
<ul className='wrapper' style={wrapperSty}>
{cloneData.map((item, index) => {
let {title, pic} = item;
return <li key={index}>
<img src={pic} alt={title}/>
</li>;
})}
</ul>
<ul className='focus'>
{data.map((item, index) => {
let tempIndex = step - 1;
step === 0 ? tempIndex = data.length - 1 : null;
step === (cloneData.length - 1) ? tempIndex = 0 : null;
return <li className={tempIndex === index ? 'active' : ''}
key={index}></li>;
})}
</ul>
<a href="javascript:;" className="arrow arrowLeft"></a>
<a href="javascript:;" className="arrow arrowRight"></a>
</section>;
}
//=>向右切换
autoMove = () => {
this.setState({
step: this.state.step + 1
});
};
}
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import Banner from "./component/Banner";
//=>公共的样式资源在INDEX中导入(组件独有的样式可以在组件内部导入)
import './static/css/reset.min.css';
//=>在REACT的JSX中需要使用图片等资源的时候,资源的地址不能使用相对地址(因为经过WEBPACK编译后,资源地址的路径已经改变了,原有的相对地址无法找到对应的资源),此时我们需要基于ES6 Module或者CommonJS等模块导入规范,把资源当做模块导入进来(或者我们使用的图片地址都是网络地址)
let IMG_DATA = [];
for (let i = 1; i <= 5; i++) {
IMG_DATA.push({
id: i,
title: '',
pic: require(`./static/images/${i}.jpg`)
});
}
//=>调取组件渲染
ReactDOM.render(<main>
{/*
* DATA:轮播图需要绑定的数据 (空数组)
* INTERVAL:自动轮播间隔的时间 (3000MS)
* STEP:默认展示图片的索引(记住前后各克隆了一张) (1)
* SPEED:每一张切换所需要的运动时间 (300MS)
*/}
<Banner data={IMG_DATA}
interval={1000}
step={1}
speed={300}/>
<div style={{margin: '20px auto'}}></div>
<Banner data={IMG_DATA.slice(2)}
interval={5000}
step={3}/>
</main>, root);
Banner.css
.container {
position: relative;
margin: 0 auto;
width: 1000px;
height: 300px;
overflow: hidden;
text-align: center;
}
.container:hover .arrow {
display: block;
}
.container .wrapper {
position: absolute;
top: 0;
left: 0;
width: 6000px;
height: 100%;
overflow: hidden;
}
.container .wrapper li {
float: left;
width: 1000px;
height: 100%;
}
.container .wrapper li img {
display: block;
width: 100%;
height: 100%;
}
.container .focus {
display: inline-block;
position: relative;
z-index: 999;
top: 270px;
padding: 4px;
font-size: 0;
background: rgba(0, 0, 0, 0.3);
border-radius: 20px;
}
.container .focus li {
display: inline-block;
margin: 0 4px;
width: 12px;
height: 12px;
background: rgba(255, 255, 255, .8);
border-radius: 50%;
cursor: pointer;
}
.container .focus li.active {
background: lightcoral;
}
.container .arrow {
display: none;
position: absolute;
top: 50%;
margin-top: -22.5px;
width: 28px;
height: 45px;
background: url("../images/pre.png") no-repeat;
opacity: 0.5;
}
.container .arrow:hover {
opacity: 1;
}
.container .arrow.arrowLeft {
left: 0;
}
.container .arrow.arrowRight {
right: 0;
background-position: -50px 0;
}
实现轮播图左右切换
Banner.js
import React from 'react';
import '../static/css/banner.css';
export default class Banner extends React.Component {
static defaultProps = {
data: [],
interval: 3000,
step: 1,
speed: 300
};
constructor(props) {
super(props);
//=>初始化状态
let {step, speed} = this.props;
this.state = {
step,
speed
};
}
componentWillMount() {
/*数据克隆*/
let {data} = this.props,
cloneData = data.slice(0);
cloneData.push(data[0]);
cloneData.unshift(data[data.length - 1]);
this.cloneData = cloneData;
}
componentDidMount() {
/*开启自动轮播*/
this.autoTimer = setInterval(this.moveRight, this.props.interval);
}
componentWillUpdate(nextProps, nextState) {
/*向右边界判断:如果当前最新修改的STEP值已经大于最大的索引,说明不能继续向右走了,我们应该让其“立即”(无动画)回到真实第一张(STEP=1)*/
if (nextState.step > (this.cloneData.length - 1)) {
this.setState({
step: 1,
speed: 0
});
}
/*向左边界判断:如果当前最新修改的索引已经小于零,说明不能继续向左走了,我们让其立即回到“倒数第二张”图片位置(真实的最后一张图片) STEP=CLONE-DATA.LENGTH-2*/
if (nextState.step < 0) {
this.setState({
step: this.cloneData.length - 2,
speed: 0
});
}
}
componentDidUpdate() {
/*向右边界判断:立即回到第一张后,我们应该让其运动到真实的第二张*/
let {step, speed} = this.state;
if (step === 1 && speed === 0) {
let delayTimer = setTimeout(() => {
clearTimeout(delayTimer);
this.setState({
step: step + 1,
speed: this.props.speed
});
}, 0);
}
/*向左边界判断:立即回到“倒数第二张”后,我们应该让其向回在运动一张*/
if (step === this.cloneData.length - 2 && speed === 0) {
let delayTimer = setTimeout(() => {
clearTimeout(delayTimer);
this.setState({
step: step - 1,
speed: this.props.speed
});
}, 0);
}
}
render() {
let {data, style} = this.props,
{cloneData} = this;
if (data.length === 0) return '';
//=>计算WRAPPER样式
let {step, speed} = this.state,
wrapperStyle = {
width: `${cloneData.length * 1000}px`,
transform: `translateX(${-step * 1000}px)`,
transition: `transform ${speed}ms`
};
return <section className={'container'}
onMouseEnter={this.movePause}
onMouseLeave={this.movePlay}
onClick={this.handleClick}>
<ul className={'wrapper'} style={wrapperStyle}
onTransitionEnd={() => {
//=>当WRAPPER切换动画完成(切换完成),再去执行下一次切换任务
this.isRun = false;
}}>
{cloneData.map((item, index) => {
let {pic, title} = item;
return <li key={index}>
<img src={pic} alt={title}/>
</li>;
})}
</ul>
<ul className={'focus'}>
{data.map((item, index) => {
/*焦点对齐:图片索引减去一就是焦点选中项对应的索引(特殊的:如果图片索引是零,让最后一个焦点选中,如果图片索引是最大,让第一个焦点选中)*/
let tempIndex = step - 1;
step === 0 ? tempIndex = data.length - 1 : null;
step === cloneData.length - 1 ? tempIndex = 0 : null;
return <li key={index} className={index === tempIndex ? 'active' : ''}></li>;
})}
</ul>
<a href="javascript:;" className="arrow arrowLeft"></a>
<a href="javascript:;" className="arrow arrowRight"></a>
</section>;
}
//=>向右切换:自动轮播或者点击有切换按钮
moveRight = () => {
this.setState({
step: this.state.step + 1
});
};
//=>自动轮播的暂停和开启
movePause = () => clearInterval(this.autoTimer);
movePlay = () => this.autoTimer = setInterval(this.moveRight, this.props.interval);
//=>事件委托
handleClick = ev => {
let target = ev.target,
tarTag = target.tagName,
tarClass = target.className;
//=>左右切换按钮
if (tarTag === 'A' && /(^| +)arrow( +|$)/.test(tarClass)) {
//=>防止过快点击
if (this.isRun) return;
this.isRun = true;
//=>RIGHT
if (tarClass.indexOf('arrowRight') >= 0) {
this.moveRight();
return;
}
//=>LEFT
this.setState({
step: this.state.step - 1
});
return;
}
};
}
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import ReactSwipe from 'react-swipe';
/*导入公共样式和资源*/
import './static/css/reset.min.css';
import './static/css/index.css';
import Banner from "./component/Banner";
/*准备数据:在JSX中需要加载一些资源(例如IMG图片),此时我们不能使用相对地址,WEBPACK打包后资源地址都是要改变的,可以使用网络绝对地址或者基于模块规范把图片导入进来使用*/
let IMG_DATA = [];
for (let i = 1; i <= 3; i++) {
IMG_DATA.push({
id: i,
title: '',
pic: require(`./static/images/${i}.jpg`)
});
}
/*RENDER渲染*/
ReactDOM.render(<main>
{/!*BANNER*!/}
<Banner data={IMG_DATA}
interval={1000}
step={1}
speed={300}/>
<Banner data={IMG_DATA}
interval={3000}/>
</main>, root);