本人前端小菜鸟,大佬们轻喷呜呜呜javascript
由于本人采用的是React + Typescript来作项目的,你们若是有看不懂也不要紧的!换成普通的html跟js也差很少的。css
<Slider cityList={cityList} row={2} step={2} />
复制代码
import React, { Component, ComponentType } from 'react'
import { Icon } from 'antd'
import './index.scss'
interface IProps {
cityList: Array<number>,
row: number,
step: number
}
interface IStates {
cityContainerWidth: number,
cityWrapWidth: number,
cityWrapTranslateX: number
}
class Slider extends Component<IProps, IStates> {
constructor(props: IProps) {
super(props)
this.state = {
cityContainerWidth: 0,
cityWrapWidth: 0,
cityWrapTranslateX: 0
}
}
componentDidMount(): void {
const { cityList, row } = this.props
const cityWrapWidth: number = cityList.length > 12 ? Math.ceil(cityList.length / row) * 220 : 1320
const cityWrapDom: HTMLElement | null = document.getElementById('city__wrap') as HTMLElement
const cityContainerDom: HTMLElement | null = document.getElementById('city__container') as HTMLElement
const cityContainerWidth: number = cityContainerDom.offsetWidth
cityWrapDom && (cityWrapDom.style.width = `${cityWrapWidth}px`)
this.setState({
cityContainerWidth,
cityWrapWidth
})
}
handleArrowClick(direction: string): void {
const { step } = this.props
const { cityContainerWidth, cityWrapWidth, cityWrapTranslateX } = this.state
const cityWrapDom: HTMLElement | null = document.getElementById('city__wrap') as HTMLElement
/* 步长 */
const translateStep: number = 220 * step
const translateDistance: number = translateStep * (direction === 'left' ? 1 : -1)
let newTranslateX: number = cityWrapTranslateX
/* 相对移动距离 */
const relativeTranslateX: number = cityContainerWidth - cityWrapTranslateX
const isLeftEnd: boolean = relativeTranslateX <= cityContainerWidth
const isLeftOverflow: boolean = relativeTranslateX - translateDistance <= cityContainerWidth
const isRightEnd: boolean = relativeTranslateX + 10 >= cityWrapWidth // 这个10是表明右边距的10个像素,加上10隐藏
const isRightOverflow: boolean = relativeTranslateX - translateDistance >= cityWrapWidth
/* 点击左箭头 */
if (translateDistance > 0) {
/* 是否到达左边尽头 */
if (isLeftEnd) return
if (isLeftOverflow) {
/* 超出范围,则滑动恰好到达左边末尾的距离 */
newTranslateX = 0
} else {
/* 未超出范围,滑动距离直接与步长相加 */
newTranslateX += translateDistance
}
} else if (translateDistance < 0) {
/* 是否到达右边尽头 */
if (isRightEnd) return
if (isRightOverflow) {
/* 超出范围,则滑动恰好到达右边末尾的距离 */
newTranslateX += relativeTranslateX + 10 - cityWrapWidth
} else {
/* 未超出范围,滑动距离直接与步长相加 */
newTranslateX += translateDistance
}
}
const transformString: string = `translateX(${newTranslateX}px)`
cityWrapDom && (cityWrapDom.style.transform = transformString)
this.setState({
cityWrapTranslateX: newTranslateX
})
}
render() {
const { cityList } = this.props
return (
<div className="city" >
<div className="city__title">我是一个轮播图</div>
<div className="city__container" id="city__container">
<div className="city__arrow" onClick={() => this.handleArrowClick('left')}>
<Icon className="icon" type="left" />
</div>
<div className="city__arrow city__arrow--right" onClick={() => this.handleArrowClick('right')}>
<Icon className="icon" type="right" />
</div>
<div className="city__wrap" id="city__wrap">
{cityList.map(item => (
<div className="city__item" key={item}>{item}</div>
))}
</div>
</div>
</div>
)
}
}
export default Slider as ComponentType<IProps>
复制代码
.city {
&__container {
position: relative;
overflow: hidden;
width: 1200px;
padding-top: 10px;
&::-webkit-scrollbar {
width: 15px;
height: 15px;
}
&::-webkit-scrollbar-track {
border-radius: 20px;
background: #e7e7e7;
}
&::-webkit-scrollbar-thumb {
background: #66a6ff;
background-image: linear-gradient(120deg, #89a4fe 0%, #66a6ff 100%);
border-radius: 20px;
}
}
&__title {
margin-top: 30px;
color: #333;
font-size: 24px;
font-weight: bold;
}
&__arrow {
position: absolute;
display: flex;
justify-content: center;
align-items: center;
top: 50%;
width: 50px;
height: 100px;
background: rgba(0, 0, 0, 0.7);
transform: translateY(-50%);
transition: all .3s ease;
z-index: 2;
opacity: .5;
cursor: pointer;
&--right {
right: 0;
}
.icon {
color: #fff;
font-size: 30px;
}
&:hover{
opacity: 1;
}
}
&__wrap {
transition: all .3s ease-in-out;
}
&__item {
float: left;
width: 210px;
height: 90px;
margin: 0 10px 10px 0;
color: #fff;
font-size: 40px;
font-weight: bold;
line-height: 90px;
text-align: center;
// background: url(https://static.zhipin.com/zhipin-geek/v98/web/geek/images/city_101010100.png) no-repeat;
// background-size: cover;
background-image: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
}
复制代码
itemList
这是一个将要用于展现的数据列表。这里咱们能够用函数生成一个展现用的列表,你们若是有须要的话能够将其换成访问接口获取的列表...html
getCityList(): Promise<Array<number>> {
return new Promise(async (resolve, reject) => {
const length: number = 15
const cityList: Array<number> = Array.from({ length }, (_: unknown, index: number): number => index + 1)
resolve(cityList)
})
}
复制代码
咱们在这里生成了一个长度为15的天然数数组[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
前端
虽然代码中都有注释,可是仍是简要的分享一下吧~java
步骤固然是: 获取须要展现的数据列表 -> 给滑动轮播组件传入列表 -> 接受后根据列表的长度、须要展现的行数来对容器设置动态宽度react
在接下来就是重头戏啦!!!web
关于处理容器如何滑动的地方我但是下了很多功夫,咱们一点一点来看吧数组
handleArrowClick(direction)
,这个函数的意思是,点击左箭头的时候,direction就是left
,反之这是right
。<div className="city__arrow" onClick={() => this.handleArrowClick('left')}>
<Icon className="icon" type="left" />
</div>
<div className="city__arrow city__arrow--right" onClick={() => this.handleArrowClick('right')}>
<Icon className="icon" type="right" />
</div>
复制代码
分别是左右箭头,点击左箭头就给函数传入一个字符串表明方向sass
/* 步长 */
const translateStep: number = 220 * step
const translateDistance: number = translateStep * (direction === 'left' ? 1 : -1)
复制代码
这个很简单,就是根据父组件传入的step
步长(每次点击滑动多少个元素),以及点击按钮时传入的方向。若是是左,则移动的距离是-步长
,在这里咱们用的是transform: translateX的属性来进行滑动,正轴是向右,因此若是要想展现的内容向右移动,则须要将移动的距离设置为负数,这样相对移动起来,才能达到完美想要的效果,如今不懂不要紧,先继续日后看一看,bash
首先咱们第一步先设置state,里面定义一个变量,来表示已经滑动的距离,初始为0
/* 已经滑动的距离 */
this.state = {
cityWrapTranslateX: 0
}
复制代码
而后咱们获取一下滑动组件的外容器跟内容器的宽度,若是不懂的话能够百度一下轮播图的实现原理,简单说明一下的话就是说
外容器就是可见的部分,而内容器就是一个部分会隐藏的,而后根据外容器的大小,就能够吧内容器里面的内容展现出来,就是图中重叠的部分啦,想象一下~内容器在左右移动的时候,咱们见到的外容器中的东西是否是也在跟着移动了嘞~
好,不懂的能够去百度找一下更为详细的轮播图原理!俺先继续往下分析
/* 相对移动距离 */
const relativeTranslateX: number = cityContainerWidth - cityWrapTranslateX
复制代码
而后咱们经过获取到来的外容器的大小cityContainerWidth
以及已经滑动的距离cityWrapTranslateX
经过二者相减就能够获得相对移动距离啦,这是什么意思呢,咱们先继续下去讲一下各类边界条件你们就懂啦!
const isLeftEnd: boolean = relativeTranslateX <= cityContainerWidth
const isLeftOverflow: boolean = relativeTranslateX - translateDistance <= cityContainerWidth
const isRightEnd: boolean = relativeTranslateX + 10 >= cityWrapWidth // 这个10是表明右边距的10个像素,加上10隐藏
const isRightOverflow: boolean = relativeTranslateX - translateDistance >= cityWrapWidth
复制代码
这四个变量分别是:是否已经到达左边尽头(不能再点击了!)、点击按钮左边是否会溢出(就是滑动过头了!)、是否到达右边尽头、点击按钮右边是否会溢出
咱们就主要分析后两个就好,后两个就大同小异啦(由于喜欢右边)
咱们先假设外容器的宽度为1200,内容器的宽度为2000
/* 相对移动距离 */
const relativeTranslateX: number = cityContainerWidth - cityWrapTranslateX
const isRightEnd: boolean = relativeTranslateX + 10 >= cityWrapWidth // 这个10是表明右边距的10个像素,加上10隐藏
复制代码
咱们先忽略这个10,由于每一个元素有外边距10px,因此咱们加上,咱们大可先忽略不计
接下来假设咱们向右移动800px,那么cityWrapTranslateX
就是-800(总之记住内容向左就是负数!)这时候咱们相对移动距离就是1200-(-800)就是2000,跟内容器同样了
接下来在看这个const isRightEnd: boolean = relativeTranslateX + 10 >= cityWrapWidth
是否是就明白了,若是大于等于,就表明到尽头了,再次点击就直接return不执行就好
由于滑动过分而致使右边产生大量空白
因此咱们这里判断一下相对移动距离是否是超出内容器的宽度,也就是说超出展现内容的宽度
const relativeTranslateX: number = cityContainerWidth - cityWrapTranslateX
const isRightOverflow: boolean = relativeTranslateX - translateDistance >= cityWrapWidth
复制代码
咱们看一下图
就是如今滑动的地方,已经只剩下300px就到尽头了,可是咱们设置的每次滑动600px,那这时候,咱们相对移动距离就是1200 - (-500)就是1700,接着relativeTranslateX - translateDistance
就是1700 - (-600)就是2300, 比2000大,就表明将要溢出,溢出了300px那么多,
咱们这时候就定义一个变量newTranslateX
来表示新的滑动距离。默认是上一次滑动的距离
let newTranslateX: number = cityWrapTranslateX
复制代码
若是是将要溢出的话,咱们就设置为上一次的滑动距离加上相对滑动距离再减去内容器的宽度,这样说可能看不懂,那咱们就来算一算
newTranslateX = 500 (已经滑动了500)
relativeTranslateX = 1200
cityWrapWidth = 2000
newTranslateX = 500 + 1200 - 2000 = -300
这样是否是就表明向右移动300个像素了呢!,这样就刚恰好到达末尾啦!
newTranslateX += relativeTranslateX - cityWrapWidth
复制代码
最后咱们再根据各类状况设置最新滑动距离就能够了,设置完滑动的距离,就能够改变样式啦
/* 点击左箭头 */
if (translateDistance > 0) {
/* 是否到达左边尽头 */
if (isLeftEnd) return
if (isLeftOverflow) {
/* 超出范围,则滑动恰好到达左边末尾的距离 */
newTranslateX = 0
} else {
/* 未超出范围,滑动距离直接与步长相加 */
newTranslateX += translateDistance
}
} else if (translateDistance < 0) {
/* 是否到达右边尽头 */
if (isRightEnd) return
if (isRightOverflow) {
/* 超出范围,则滑动恰好到达右边末尾的距离 */
newTranslateX += relativeTranslateX + 10 - cityWrapWidth
} else {
/* 未超出范围,滑动距离直接与步长相加 */
newTranslateX += translateDistance
}
}
const transformString: string = `translateX(${newTranslateX}px)`
cityWrapDom && (cityWrapDom.style.transform = transformString)
this.setState({
cityWrapTranslateX: newTranslateX
})
复制代码
const { cityList, row } = this.props
const cityWrapWidth: number = cityList.length > 12 ? Math.ceil(cityList.length / row) * 220 : 1320
const cityWrapDom: HTMLElement | null = document.getElementById('city__wrap') as HTMLElement
cityWrapDom && (cityWrapDom.style.width = `${cityWrapWidth}px`)
复制代码
这里就是简单的判断多少个元素,咱们默认显示12个,两行,一行6个这样子,不满12个就分开展现
设置为三行的时候: