轮播图的实现是面试中经常闻到的一个问题,我们今天就来实现一个简单的轮播图,并且看一下Angular
和React
版本的Ant Design
中轮播图是怎么实现的。
简单实现轮播图
网上有很多轮播图的实现方法,主要思路就像图中表示的一样,将所有的图片平铺,通过设置偏移量,将当前图片移动到可视区域。 我们通过代码来实现一下: 首先定义HTML模版
<div class="container">
<div class="wrap" style="left:-600px;">
<img src='./imgs/1.jpg'>
<img src='./imgs/2.jpg'>
<img src='./imgs/3.jpg'>
<img src='./imgs/4.jpg'>
</div>
<div class="buttons">
<span>1</span>
<span>2</span>
<span>3</span>
<span>4</span>
</div>
<a href="javascript:;" class="arrow arrow_left"><</a>
<a href="javascript:;" class="arrow arrow_right">></a>
</div>
这里主要包括三部分:轮播图的图片列表,轮播切换的按钮,左右切换按钮; 然后定义一下样式
.container {
position: relative;
width: 600px;
height: 400px;
margin:100px auto 0 auto;
box-shadow: 0 0 5px green;
overflow: hidden;
}
.wrap {
position: absolute;
width: 4200px;
height: 400px;
z-index: 1;
}
.container .wrap img {
float: left;
width: 600px;
height: 400px;
line-height: 400px;
color: #FFF;
text-align: center;
background: rgb(54, 77, 121);
}
.container .buttons {
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom:20px;
height: 10px;
z-index: 2;
}
.container .arrow {
position: absolute;
top: 35%;
color: green;
padding:0px 14px;
border-radius: 50%;
font-size: 50px;
z-index: 2;
display: none;
}
我在这里没有把全部样式代码写上,反正也不重要,大家可以自由发挥。在样式里有几个要注意的点:
- 图片只在container中,所以需要限定其宽度和高度并且使用overflow:hidden;将其余的图片隐藏起来,并且我们希望wrap相对于container左右移动,所以设置为relative
.wrap{width: 2400px}
这里是包裹图片的元素,我们例子中有4张图片,每张600px
,实际上宽度应该根据内容动态设置,我们这里先写死,文章后面会改进一下;img {width: 600px;height: 400px;}
代码中也写的固定值,后面处理。
现在我们的轮播图是这个样子的。
下面看下javascript实现 var wrap = document.querySelector(".wrap");
var next = document.querySelector(".arrow_right");
var prev = document.querySelector(".arrow_left");
var currIndex = 1;
var len = wrap.children.length;
next.onclick = function () {
next_pic();
}
prev.onclick = function () {
prev_pic();
}
function next_pic () {
newLeft;
if(currIndex === len){
newLeft = 0;
currIndex = 1;
}else{
newLeft = parseInt(wrap.style.left)-600;
currIndex ++
}
wrap.style.left = newLeft + "px";
}
function prev_pic () {
var newLeft;
if(currIndex === 1){
newLeft = -1800;
currIndex = 4;
}else{
newLeft = parseInt(wrap.style.left)+600;
currIndex--;
}
wrap.style.left = newLeft + "px";
}
首先要获取模版中的.wrap
元素,我们的轮播效果就是通过设置它的偏移量实现的。然后获取左右切换按钮,并添加了切换的事件。
var newLeft = parseInt(wrap.style.left)-600;
wrap.style.left = newLeft + "px";
我们设置.wrap
的position
属性是absolute
,所以只需要修改left
的值就可以。要注意的是wrap.style.left
是字符串,所以要转换成数字。
currIndex
表示当前显示的图片索引,从1
开始;len
表示图片的个数。当显示的图片为最后一张时,再点击“下一张”,就会将图片定位到第一张,索引修改为第一张的标识。
现在我们是通过点击按钮实现的切换,那么下面添加自动播放,只需要使用setInterval
方法循环切换就可以了。
var timer = null;
function autoPlay () {
timer = setInterval(function () {
next_pic();
},1000);
}
autoPlay();
现在的图片切换过程非常生硬,我们来加上一个过度动画,实现平滑的切换。
.wrap {
transition: 1s;
}
css3的动画实现非常简单,大家也可以尝试其它方法。 到现在实现了轮播图的基本功能。剩下的每个button上加切换功能就留给大家自己尝试一下。
Ant Design of Angular
(如果没有angualr开发经验可以看下思路,不用管代码) HTML模版的内容和我们自己实现的没有太大区别,只不过为了使组件更加灵活添加了一些配置项,允许业务自己扩展。
<div class="slick-initialized slick-slider" [class.slick-vertical]="nzVertical">
<div
#slickList
class="slick-list"
tabindex="-1"
(keydown)="onKeyDown($event)"
(mousedown)="pointerDown($event)"
(touchstart)="pointerDown($event)"
>
<!-- Render carousel items. -->
<div class="slick-track" #slickTrack>
<ng-content></ng-content>
</div>
</div>
<!-- Render dots. -->
<ul class="slick-dots" *ngIf="nzDots">
<li
*ngFor="let content of carouselContents; let i = index"
[class.slick-active]="content.isActive"
(click)="goTo(i)"
>
<ng-template [ngTemplateOutlet]="nzDotRender || renderDotTemplate" [ngTemplateOutletContext]="{ $implicit: i }">
</ng-template>
</li>
</ul>
</div>
<ng-template #renderDotTemplate let-index>
<button>{{ index + 1 }}</button>
</ng-template>
下面我们只看一下几个重点方法的实现:
在组件初始化之后会执行一个方法,markContentActive(0)
,将第一个图片标为当前显示,和我们前面的currIndex
一个意思。
private markContentActive(index: number): void {
this.activeIndex = index;
if (this.carouselContents) {
this.carouselContents.forEach((slide, i) => {
slide.isActive = index === i;
});
}
this.cdr.markForCheck();
}
来下看切换方法:
next(): void {
this.goTo(this.activeIndex + 1);
}
pre(): void {
this.goTo(this.activeIndex - 1);
}
goTo(index: number): void {
if (this.carouselContents && this.carouselContents.length && !this.isTransiting) {
const length = this.carouselContents.length;
const from = this.activeIndex;
const to = (index + length) % length;
this.isTransiting = true;
this.nzBeforeChange.emit({ from, to });
this.strategy.switch(this.activeIndex, index).subscribe(() => {
this.scheduleNextTransition();
this.nzAfterChange.emit(index);
this.isTransiting = false;
});
this.markContentActive(to);
this.cdr.markForCheck();
}
}
这里使用goTo
方法实现切换,接收参数为要跳转的位置索引。这里面执行了一个this.strategy.switch
方法,传入当前索引以及要跳转的索引值。
this.strategy =
this.nzEffect === 'scrollx'
? new NzCarouselTransformStrategy(this, this.cdr, this.renderer)
: new NzCarouselOpacityStrategy(this, this.cdr, this.renderer);
this.strategy
根据参数判断采用哪种切换动画。默认使用transform
.
const rect = carousel.el.getBoundingClientRect();
this.slickListEl = carousel.slickListEl;
this.slickTrackEl = carousel.slickTrackEl;
this.unitWidth = rect.width;
this.unitHeight = rect.height;
this.contents = contents ? contents.toArray() : [];
this.length = this.contents.length;
这里通过获取页面元素算出轮播图当前的width
和height
,用来计算偏移量。
withCarouselContents(contents: QueryList<NzCarouselContentDirective> | null): void {
super.withCarouselContents(contents);
const carousel = this.carouselComponent!;
const activeIndex = carousel.activeIndex;
if (this.contents.length) {
if (this.vertical) {
this.renderer.setStyle(this.slickListEl, 'height', `${this.unitHeight}px`);
this.renderer.setStyle(this.slickTrackEl, 'height', `${this.length * this.unitHeight}px`);
this.renderer.setStyle(
this.slickTrackEl,
'transform',
`translate3d(0, ${-activeIndex * this.unitHeight}px, 0)`
);
} else {
this.renderer.setStyle(this.slickTrackEl, 'width', `${this.length * this.unitWidth}px`);
this.renderer.setStyle(this.slickTrackEl, 'transform', `translate3d(${-activeIndex * this.unitWidth}px, 0, 0)`);
}
this.contents.forEach((content: NzCarouselContentDirective) => {
this.renderer.setStyle(content.el, 'position', 'relative');
this.renderer.setStyle(content.el, 'width', `${this.unitWidth}px`);
});
}
}
与我们修改left
属性不同,这里使用transform
属性切换位置。
在这个里面有一个scheduleNextTransition
方法,主要用来判断是否需要自动播放,然后通过setTimeout
方法间隔一段时间之后再执行goTo
,实现了自动轮播功能。
private scheduleNextTransition(): void {
this.clearScheduledTransition();
if (this.nzAutoPlay && this.nzAutoPlaySpeed > 0 && this.platform.isBrowser) {
this.transitionInProgress = setTimeout(() => {
this.goTo(this.activeIndex + 1);
}, this.nzAutoPlaySpeed);
}
}
可以看出它的实现思路和我们上面实现的基本相同。
Ant Design for React
React版本的实现方法大同小异,偏移量处理也是通过transform
实现的。
if (spec.useTransform) {
let WebkitTransform = !spec.vertical
? "translate3d(" + spec.left + "px, 0px, 0px)"
: "translate3d(0px, " + spec.left + "px, 0px)";
let transform = !spec.vertical
? "translate3d(" + spec.left + "px, 0px, 0px)"
: "translate3d(0px, " + spec.left + "px, 0px)";
let msTransform = !spec.vertical
? "translateX(" + spec.left + "px)"
: "translateY(" + spec.left + "px)";
style = {
...style,
WebkitTransform,
transform,
msTransform
};
} else {
if (spec.vertical) {
style["top"] = spec.left;
} else {
style["left"] = spec.left;
}
}
而对于自动播放则选择了setInterval
。
autoPlay = playType => {
if (this.autoplayTimer) {
clearInterval(this.autoplayTimer);
}
const autoplaying = this.state.autoplaying;
if (playType === "update") {
if (
autoplaying === "hovered" ||
autoplaying === "focused" ||
autoplaying === "paused"
) {
return;
}
} else if (playType === "leave") {
if (autoplaying === "paused" || autoplaying === "focused") {
return;
}
} else if (playType === "blur") {
if (autoplaying === "paused" || autoplaying === "hovered") {
return;
}
}
this.autoplayTimer = setInterval(this.play, this.props.autoplaySpeed + 50);
this.setState({ autoplaying: "playing" });
};