本人前端小菜鳥,大佬們輕噴嗚嗚嗚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,因此咱們加上,咱們大可先忽略不計
爲何是這樣呢,假設如今咱們的移動距離爲0,那麼相對距離就是外容器的寬度了就是1200,那麼內容器爲2000,小於內容器接下來假設咱們向右移動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個就分開展現
設置爲三行的時候: