欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

使用JSX實(shí)現(xiàn)Carousel輪播組件的方法(前端組件化)

 更新時(shí)間:2021年04月06日 18:01:25   作者:三鉆  
做這個(gè)輪播圖的組件,我們先從一個(gè)最簡(jiǎn)單的 DOM 操作入手。使用 DOM 操作把整個(gè)輪播圖的功能先實(shí)現(xiàn)出來(lái),然后在一步一步去考慮怎么把它設(shè)計(jì)成一個(gè)組件系統(tǒng)

在我們用 JSX 建立組件系統(tǒng)之前,我們先來(lái)用一個(gè)例子學(xué)習(xí)一下組件的實(shí)現(xiàn)原理和邏輯。這里我們就用一個(gè)輪播圖的組件作為例子進(jìn)行學(xué)習(xí)。輪播圖的英文叫做 Carousel,它有一個(gè)旋轉(zhuǎn)木馬的意思。

上一篇文章《使用 JSX 建立 Markup 組件風(fēng)格》中我們實(shí)現(xiàn)的代碼,其實(shí)還不能稱(chēng)為一個(gè)組件系統(tǒng),頂多是可以充當(dāng) DOM 的一個(gè)簡(jiǎn)單封裝,讓我們有能力定制 DOM。

要做這個(gè)輪播圖的組件,我們應(yīng)該先從一個(gè)最簡(jiǎn)單的 DOM 操作入手。使用 DOM 操作把整個(gè)輪播圖的功能先實(shí)現(xiàn)出來(lái),然后在一步一步去考慮怎么把它設(shè)計(jì)成一個(gè)組件系統(tǒng)。

TIPS:在開(kāi)發(fā)中我們往往一開(kāi)始做一個(gè)組件的時(shí)候,都會(huì)過(guò)度思考一個(gè)功能應(yīng)該怎么設(shè)計(jì),然后就把它實(shí)現(xiàn)的非常復(fù)雜。其實(shí)更好的方式是反過(guò)來(lái)的,先把功能實(shí)現(xiàn)了,然后通過(guò)分析這個(gè)功能從而設(shè)計(jì)出一個(gè)組件架構(gòu)體系。

因?yàn)槭禽啿D,那我們當(dāng)然需要用到圖片,所以這里我準(zhǔn)備了 4 張來(lái)源于 Unsplash 的開(kāi)源圖片,當(dāng)然大家也可以換成自己的圖片。首先我們把這 4 張圖片都放入一個(gè) gallery 的變量當(dāng)中:

let gallery = [
 'https://source.unsplash.com/Y8lCoTRgHPE/1142x640',
 'https://source.unsplash.com/v7daTKlZzaw/1142x640',
 'https://source.unsplash.com/DlkF4-dbCOU/1142x640',
 'https://source.unsplash.com/8SQ6xjkxkCo/1142x640',
];

而我們的目標(biāo)就是讓這 4 張圖可以輪播起來(lái)。


組件底層封裝

首先我們需要給我們之前寫(xiě)的代碼做一下封裝,便于我們開(kāi)始編寫(xiě)這個(gè)組件。

  • 根目錄建立 framework.js
  • createElement、 ElementWrapper、TextWrapper 這三個(gè)移到我們的 framework.js 文件中
  • 然后 createElement 方法是需要 export 出去讓我們可以引入這個(gè)基礎(chǔ)創(chuàng)建元素的方法。
  • ElementWrapperTextWrapper 是不需要 export 的,因?yàn)樗鼈兌紝儆趦?nèi)部給 createElement 使用的
  • 封裝 Wrapper 類(lèi)中公共部分
  • ElementWrapper、TextWrapper之中都有一樣的 setAttributeappendChildmountTo ,這些都是重復(fù)并且可公用的
  • 所以我們可以建立一個(gè) Component 類(lèi),把這三個(gè)方法封裝進(jìn)入
  • 然后讓 ElementWrapperTextWrapper 繼承 Component
  • Component 加入 render() 方法
  •      在 Component 類(lèi)中加入 構(gòu)造函數(shù)

這樣我們就封裝好我們組件的底層框架的代碼,代碼示例如下:

function createElement(type, attributes, ...children) {
 // 創(chuàng)建元素
 let element;
 if (typeof type === 'string') {
 element = new ElementWrapper(type);
 } else {
 element = new type();
 }

 // 掛上屬性
 for (let name in attributes) {
 element.setAttribute(name, attributes[name]);
 }
 // 掛上所有子元素
 for (let child of children) {
 if (typeof child === 'string') child = new TextWrapper(child);
 element.appendChild(child);
 }
 // 最后我們的 element 就是一個(gè)節(jié)點(diǎn)
 // 所以我們可以直接返回
 return element;
}

export class Component {
 constructor() {
 }
 // 掛載元素的屬性
 setAttribute(name, attribute) {
 this.root.setAttribute(name, attribute);
 }
 // 掛載元素子元素
 appendChild(child) {
 child.mountTo(this.root);
 }
 // 掛載當(dāng)前元素
 mountTo(parent) {
 parent.appendChild(this.root);
 }
}

class ElementWrapper extends Component {
 // 構(gòu)造函數(shù)
 // 創(chuàng)建 DOM 節(jié)點(diǎn)
 constructor(type) {
 this.root = document.createElement(type);
 }
}

class TextWrapper extends Component {
 // 構(gòu)造函數(shù)
 // 創(chuàng)建 DOM 節(jié)點(diǎn)
 constructor(content) {
 this.root = document.createTextNode(content);
 }
}

實(shí)現(xiàn) Carousel

接下來(lái)我們就要繼續(xù)改造我們的 main.js。首先我們需要把 Div 改為 Carousel 并且讓它繼承我們寫(xiě)好的 Component 父類(lèi),這樣我們就可以省略重復(fù)實(shí)現(xiàn)一些方法。

繼承了 Component后,我們就要從 framework.js 中 import 我們的 Component。

這里我們就可以正式開(kāi)始開(kāi)發(fā)組件了,但是如果每次都需要手動(dòng) webpack 打包一下,就特別的麻煩。所以為了讓我們可以更方便的調(diào)試代碼,這里我們就一起來(lái)安裝一下 webpack dev server 來(lái)解決這個(gè)問(wèn)題。

執(zhí)行一下代碼,安裝 webpack-dev-server

npm install --save-dev webpack-dev-server webpack-cli

看到上面這個(gè)結(jié)果,就證明我們安裝成功了。我們最好也配置一下我們 webpack 服務(wù)器的運(yùn)行文件夾,這里我們就用我們打包出來(lái)的 dist 作為我們的運(yùn)行目錄。

設(shè)置這個(gè)我們需要打開(kāi)我們的 webpack.config.js,然后加入 devServer 的參數(shù), contentBase 給予 ./dist 這個(gè)路徑。

module.exports = {
 entry: './main.js',
 mode: 'development',
 devServer: {
 contentBase: './dist',
 },
 module: {
 rules: [
 {
 test: /\.js$/,
 use: {
 loader: 'babel-loader',
 options: {
 presets: ['@babel/preset-env'],
 plugins: [['@babel/plugin-transform-react-jsx', { pragma: 'createElement' }]],
 },
 },
 },
 ],
 },
};

用過(guò) Vue 或者 React 的同學(xué)都知道,啟動(dòng)一個(gè)本地調(diào)試環(huán)境服務(wù)器,只需要執(zhí)行 npm 命令就可以了。這里我們也設(shè)置一個(gè)快捷啟動(dòng)命令。打開(kāi)我們的 package.json,在 scripts 的配置中添加一行 "start": "webpack start" 即可。

{
 "name": "jsx-component",
 "version": "1.0.0",
 "description": "",
 "main": "index.js",
 "scripts": {
 "test": "echo \"Error: no test specified\" && exit 1",
 "start": "webpack serve"
 },
 "author": "",
 "license": "ISC",
 "devDependencies": {
 "@babel/core": "^7.12.3",
 "@babel/plugin-transform-react-jsx": "^7.12.5",
 "@babel/preset-env": "^7.12.1",
 "babel-loader": "^8.1.0",
 "webpack": "^5.4.0",
 "webpack-cli": "^4.2.0",
 "webpack-dev-server": "^3.11.0"
 },
 "dependencies": {}
}

這樣我們就可以直接執(zhí)行下面這個(gè)命令啟動(dòng)我們的本地調(diào)試服務(wù)器啦!

npm start

開(kāi)啟了這個(gè)之后,當(dāng)我們修改任何文件時(shí)都會(huì)被監(jiān)聽(tīng)到,這樣就會(huì)實(shí)時(shí)給我們打包文件,非常方便我們調(diào)試??吹缴蠄D里面表示,我們的實(shí)時(shí)本地服務(wù)器地址就是 http://localhost:8080。我們?cè)跒g覽器直接打開(kāi)這個(gè)地址就可以訪問(wèn)這個(gè)項(xiàng)目。

這里要注意的一個(gè)點(diǎn),我們把運(yùn)行的目錄改為了 dist,因?yàn)槲覀冎暗?main.html 是放在根目錄的,這樣我們就在 localhost:8080 上就找不到這個(gè) HTML 文件了,所以我們需要把 main.html 移動(dòng)到 dist 目錄下,并且改一下 main.js 的引入路徑。

<!-- main.html 代碼 -->
<body></body>

<script src="./main.js"></script>

打開(kāi)鏈接后我們發(fā)現(xiàn) Carousel 組件已經(jīng)被掛載成功了,這個(gè)證明我們的代碼封裝是沒(méi)有問(wèn)題的。

接下來(lái)我們繼續(xù)來(lái)實(shí)現(xiàn)我們的輪播圖功能,首先要把我們的圖片數(shù)據(jù)傳進(jìn)去我們的 Carousel 組件里面。

let a = <Carousel src={gallery}/>;

這樣我們的 gallery 數(shù)組就會(huì)被設(shè)置到我們的 src 屬性上。但是我們的這個(gè) src 屬性不是給我們的 Carousel 自身的元素使用的。也就說(shuō)我們不是像之前那樣直接掛載到 this.root 上。

所以我們需要另外儲(chǔ)存這個(gè) src 上的數(shù)據(jù),后面使用它來(lái)生成我們輪播圖的圖片展示元素。在 React 里面是用 props 來(lái)儲(chǔ)存元素屬性,但是這里我們就用一個(gè)更加接近屬性意思的 attributes 來(lái)儲(chǔ)存。

因?yàn)槲覀冃枰獌?chǔ)存進(jìn)來(lái)的屬性到 this.attributes 這個(gè)變量中,所以我們需要在 Component 類(lèi)的 constructor 中先初始化這個(gè)類(lèi)屬性。

然后這個(gè) attributes 是需要我們另外存儲(chǔ)到類(lèi)屬性中,而不是掛載到我們?cè)毓?jié)點(diǎn)上。所以我們需要在組件類(lèi)中重新定義我們的 setAttribute 方法。

我們需要在組件渲染之前能拿到 src 屬性的值,所以我們需要把 render 的觸發(fā)放在 mountTo 之內(nèi)。

class Carousel extends Component {
 // 構(gòu)造函數(shù)
 // 創(chuàng)建 DOM 節(jié)點(diǎn)
 constructor() {
 super();
 this.attributes = Object.create(null);
 }
 setAttribute(name, value) {
 this.attributes[name] = value;
 }
 render() {
	console.log(this.attributes);
 return document.createElement('div');
 }
 mountTo() {
 parent.appendChild(this.render());
 }
}

接下來(lái)我們看看實(shí)際運(yùn)行的結(jié)果,看看是不是能夠獲得圖片的數(shù)據(jù)。

接下來(lái)我們就去把這些圖給顯示出來(lái)。這里我們需要改造一下 render 方法,在這里加入渲染圖片的邏輯:

  • 首先我們需要把創(chuàng)建的新元素儲(chǔ)起來(lái)
  • 循環(huán)我們的圖片數(shù)據(jù),給每條數(shù)據(jù)創(chuàng)建一個(gè) img 元素
  • 給每一個(gè) img 元素附上 src = 圖片 url
  • 把附上 src 屬性的圖片元素掛載到我們的組件元素 this.root
  • 最后讓 render 方法返回 this.root
class Carousel extends Component {
 // 構(gòu)造函數(shù)
 // 創(chuàng)建 DOM 節(jié)點(diǎn)
 constructor() {
 super();
 this.attributes = Object.create(null);
 }
 setAttribute(name, value) {
 this.attributes[name] = value;
 }
 render() {
 this.root = document.createElement('div');

 for (let picture of this.attributes.src) {
 let child = document.createElement('img');
 child.src = picture;
 this.root.appendChild(child);
 }

 return this.root;
 }
 mountTo(parent) {
 parent.appendChild(this.render());
 }
}

就這樣我們就可以看到我們的圖片被正確的顯示在我們的頁(yè)面上。


排版與動(dòng)畫(huà)

首先我們圖片的元素都是 img 標(biāo)簽,但是使用這個(gè)標(biāo)簽的話,當(dāng)我們點(diǎn)擊并且拖動(dòng)的時(shí)候它自帶就是可以被拖拽的。當(dāng)然這個(gè)也是可以解決的,但是為了更簡(jiǎn)單的解決這個(gè)問(wèn)題,我們就把 img 換成 div,然后使用 background-image。

默認(rèn) div 是沒(méi)有寬高的,所以我們需要在組件的 div 這一層加一個(gè) class 叫 carousel,然后在 HTML 中加入 css 樣式表,直接選擇 carousel 下的每一個(gè) div,然后給他們合適的樣式。

// main.js
class Carousel extends Component {
 // 構(gòu)造函數(shù)
 // 創(chuàng)建 DOM 節(jié)點(diǎn)
 constructor() {
 super();
 this.attributes = Object.create(null);
 }
 setAttribute(name, value) {
 this.attributes[name] = value;
 }
 render() {
 this.root = document.createElement('div');
	this.root.addClassList('carousel'); // 加入 carousel class

 for (let picture of this.attributes.src) {
 let child = document.createElement('div');
 child.backgroundImage = `url('${picture}')`;
 this.root.appendChild(child);
 }

 return this.root;
 }
 mountTo(parent) {
 parent.appendChild(this.render());
 }
}
<!-- main.html -->
<head>
 <style>
 .carousel > div {
 width: 500px;
 height: 281px;
 background-size: contain;
 }
 </style>
</head>

<body></body>

<script src="./main.js"></script>

這里我們的寬是 500px,但是如果我們?cè)O(shè)置一個(gè)高是 300px,我們會(huì)發(fā)現(xiàn)圖片的底部出現(xiàn)了一個(gè)圖片重復(fù)的現(xiàn)象。這是因?yàn)閳D片的比例是 1600 x 900,而 500 x 300 比例與圖片原來(lái)的比例不一致。

所以通過(guò)比例計(jì)算,我們可以得出這樣一個(gè)高度: 500 ÷ 1900 × 900 = 281. x x x 500\div1900\times900 = 281.xxx 500÷1900×900=281.xxx。所以 500px 寬對(duì)應(yīng)比例的高大概就是 281px。這樣我們的圖片就可以正常的顯示在一個(gè) div 里面了。

一個(gè)輪播圖顯然不可能所有的圖片都顯示出來(lái)的,我們認(rèn)知中的輪播圖都是一張一張圖片顯示的。首先我們需要讓圖片外層的 carousel div 元素有一個(gè)和它們一樣寬高的盒子,然后我們?cè)O(shè)置 overflow: hidden。這樣其他圖片就會(huì)超出盒子所以被隱藏了。

這里有些同學(xué)可能問(wèn):“為什么不把其他圖片改為 display: hidden 或者 opacity:0 呢?” 因?yàn)槲覀兊妮啿D在輪播的時(shí)候,實(shí)際上是可以看到當(dāng)前的圖片和下一張圖片的。所以如果我們用了 display: hidden 這種隱藏屬性,我們后面的效果就不好做了。

然后我們又有一個(gè)問(wèn)題,輪播圖一般來(lái)說(shuō)都是左右滑動(dòng)的,很少見(jiàn)是上下滑動(dòng)的,但是我們這里圖片就是默認(rèn)從上往下排布的。所以這里我們需要調(diào)整圖片的布局,讓它們拍成一行。

這里我們使用正常流就可以了,所以只需要給 div 加上一個(gè) display: inline-block,就可以讓它們排列成一行,但是只有這個(gè)屬性的話,如果圖片超出了窗口寬度就會(huì)自動(dòng)換行,所以我們還需要在它們父級(jí)加入強(qiáng)制不換行的屬性 white-space: nowrap。這樣我們就大功告成了。

<head>
 <style>
 .carousel {
 width: 500px;
 height: 281px;
 white-space: nowrap;
 overflow: hidden;
 }

 .carousel > div {
 width: 500px;
 height: 281px;
 background-size: contain;
 display: inline-block;
 }
 </style>
</head>

<body></body>

<script src="./main.js"></script>

接下來(lái)我們來(lái)實(shí)現(xiàn)自動(dòng)輪播效果,在做這個(gè)之前我們先給這些圖片元素加上一些動(dòng)畫(huà)屬性。這里我們用 transition 來(lái)控制元素動(dòng)效的時(shí)間,一般來(lái)說(shuō)我們播一幀會(huì)用 0.5 秒 的 ease。

Transition 一般來(lái)說(shuō)都只用 ease 這個(gè)屬性,除非是一些非常特殊的情況,ease-in 會(huì)用在推出動(dòng)畫(huà)當(dāng)中,而 ease-out 就會(huì)用在進(jìn)入動(dòng)畫(huà)當(dāng)中。在同一屏幕上的,我們一般默認(rèn)都會(huì)使用 ease,但是 linear 在大部分情況下我們是永遠(yuǎn)不會(huì)去用的。因?yàn)?ease 是最符合人類(lèi)的感覺(jué)的一種運(yùn)動(dòng)曲線。

<head>
 <style>
 .carousel {
 width: 500px;
 height: 281px;
 white-space: nowrap;
 overflow: hidden;
 }

 .carousel > div {
 width: 500px;
 height: 281px;
 background-size: contain;
 display: inline-block;
 transition: ease 0.5s;
 }
 </style>
</head>

<body></body>

<script src="./main.js"></script>

實(shí)現(xiàn)自動(dòng)輪播

有了動(dòng)畫(huà)效果屬性,我們就可以在 JavaScript 中加入我們的定時(shí)器,讓我們的圖片在每三秒鐘切換一次圖片。我們使用 setInerval() 這個(gè)函數(shù)就可以解決這個(gè)問(wèn)題了。

但是我們?cè)趺床拍茏寛D片輪播,或者移動(dòng)呢?想到 HTML 中的移動(dòng),大家有沒(méi)有想到 CSS 當(dāng)中有什么屬性可以讓我們移動(dòng)元素的呢?

對(duì)沒(méi)錯(cuò),就是使用 transform,它就是在 CSS 當(dāng)中專(zhuān)門(mén)用于挪動(dòng)元素的。所以這里我們的邏輯就是,每 3 秒往左邊挪動(dòng)一次元素自身的長(zhǎng)度,這樣我們就可以挪動(dòng)到下一張圖的開(kāi)始。

但是這樣只能挪動(dòng)一張圖,所以如果我們需要挪動(dòng)第二次,到達(dá)第三張圖,我們就要讓每一張圖偏移 200%,以此類(lèi)推。所以我們需要一個(gè)當(dāng)前頁(yè)數(shù)的值,叫做 current,默認(rèn)值為 0。每次挪動(dòng)的時(shí)候時(shí)就加一,這樣偏移的值就是 − 100 × 頁(yè) 數(shù) -100\times頁(yè)數(shù) −100×頁(yè)數(shù)。這樣我們就完成了圖片多次移動(dòng),一張一張圖片展示了。

class Carousel extends Component {
 // 構(gòu)造函數(shù)
 // 創(chuàng)建 DOM 節(jié)點(diǎn)
 constructor() {
 super();
 this.attributes = Object.create(null);
 }
 setAttribute(name, value) {
 this.attributes[name] = value;
 }
 render() {
 this.root = document.createElement('div');
 this.root.classList.add('carousel');

 for (let picture of this.attributes.src) {
 let child = document.createElement('div');
 child.style.backgroundImage = `url('${picture}')`;
 this.root.appendChild(child);
 }

 let current = 0;
 setInterval(() => {
 let children = this.root.children;
 ++current;
 for (let child of children) {
 child.style.transform = `translateX(-${100 * current}%)`;
 }
 }, 3000);

 return this.root;
 }
 mountTo(parent) {
 parent.appendChild(this.render());
 }
}

這里我們發(fā)現(xiàn)一個(gè)問(wèn)題,這個(gè)輪播是不會(huì)停止的,一直往左偏移沒(méi)有停止。而我們需要輪播到最后一張的時(shí)候是回到一張圖的。

要解決這個(gè)問(wèn)題,我們可以利用一個(gè)數(shù)學(xué)的技巧,如果我們想要一個(gè)數(shù)是在 1 到 N 之間不斷循環(huán),我們就讓它對(duì) n 取余就可以了。在我們?cè)刂?,children 的長(zhǎng)度是 4,所以當(dāng)我們 current 到達(dá) 4 的時(shí)候, 4 ÷ 4 4\div4 4÷4 的余數(shù)就是 0,所以每次把 current 設(shè)置成 current 除以 children 長(zhǎng)度的余數(shù)就可以達(dá)到無(wú)限循環(huán)了。

這里 current 就不會(huì)超過(guò) 4, 到達(dá) 4 之后就會(huì)回到 0。

用這個(gè)邏輯來(lái)實(shí)現(xiàn)我們的輪播,確實(shí)能讓我們的圖片無(wú)限循環(huán),但是如果我們運(yùn)行一下看看的話,我們又會(huì)發(fā)現(xiàn)另外一個(gè)問(wèn)題。當(dāng)我們播放到最后一個(gè)圖片之后,就會(huì)快速滑動(dòng)到第一個(gè)張圖片,我們會(huì)看到一個(gè)快速回退的效果。這個(gè)確實(shí)不是那么好,我們想要的效果是,到達(dá)最后一張圖之后,第一張圖就直接在后面接上。

那么我們就一起去嘗試解決這個(gè)問(wèn)題,經(jīng)過(guò)觀察其實(shí)在屏幕上一次最多就只能看到兩張圖片。那么其實(shí)我們就把這兩張圖片挪到正確的位置就可以了。

所以我們需要找到當(dāng)前看到的圖片,還有下一張圖片,然后每次移動(dòng)到下一張圖片就找到再下一張圖片,把下一張圖片挪動(dòng)到正確的位置。

講到這里可能還是有點(diǎn)懵,但是不要緊,我們來(lái)整理一下邏輯。

獲取當(dāng)前圖片 index 和 下一張圖的 index

  • 首先輪播肯定是從第一張圖開(kāi)始,而這張圖在我們的節(jié)點(diǎn)中肯定是第 0 個(gè)
  • 因?yàn)槲覀冃枰诳吹揭粡垐D的時(shí)候就準(zhǔn)備第二張圖,所以我們就需要找到下一張圖的位置
  • 根據(jù)我們上面說(shuō)的,下一張圖的位置,我們可以使用數(shù)學(xué)里的技巧來(lái)獲得: 下 一 張 圖 的 位 置 = ( 當(dāng) 前 位 置 + 1 ) ÷ 圖 片 數(shù) 量 下一張圖的位置 = (當(dāng)前位置 + 1)\div 圖片數(shù)量下一張圖的位置=(當(dāng)前位置+1)÷圖片數(shù)量 的余數(shù),根據(jù)這個(gè)公式,當(dāng)我們達(dá)到圖片最后一張的時(shí)候,就會(huì)返回 0,回到第一個(gè)圖片的位置

計(jì)算圖片移動(dòng)的距離,保持當(dāng)前圖片后面有一張圖片等著被挪動(dòng)過(guò)來(lái)

  • 當(dāng)前顯示的圖片的位置肯定是對(duì)的,所以我們是不需要計(jì)算的
  • 但是下一張圖片的位置就需要我們?nèi)ヅ矂?dòng)它的位置,所以這里我們需要計(jì)算這個(gè)圖片需要偏移的距離
  • 每一個(gè)圖片移動(dòng)一格的距離就是等于它自身的長(zhǎng)度,加上往左移動(dòng)是負(fù)數(shù),所以每往左邊移動(dòng)一個(gè)格就是 -100%
  • 圖片的 index 是從 0 到 n 的,如果我們用它們所在的 index 作為它們距離當(dāng)前圖片相差的圖片數(shù),我們就可以用 index * -100%,這樣就可以把每一張圖片移動(dòng)到當(dāng)前圖片的位置。
  • 但是我們需要的是先把圖片移動(dòng)到當(dāng)前圖片的下一位的位置,所以下一位的所在位置是 index - 1 的圖片距離,也就是說(shuō)我們要移動(dòng)的距離是 (index - 1) * -100%
  • 讓第二張圖就位的這個(gè)動(dòng)作,我們不需要它出現(xiàn)任何動(dòng)畫(huà)效果,所以在這個(gè)過(guò)程中我們需要禁止圖片的動(dòng)畫(huà)效果,那就要清楚 transition

第二張圖就位,就可以開(kāi)始執(zhí)行輪播效果

  • 因?yàn)樯厦嫖覀冃枰辽僖粠膱D片移動(dòng)時(shí)間,所以執(zhí)行輪播效果之前需要一個(gè) 16 毫秒的延遲 (因?yàn)?16 毫秒剛好是瀏覽器一幀的時(shí)間)
  • 首先把行內(nèi)標(biāo)簽中的 transition 重新開(kāi)啟,這樣我們 CSS 中的動(dòng)效就會(huì)重新起效,因?yàn)榻酉聛?lái)的輪播效果是需要有動(dòng)畫(huà)效果的
  • 第一步是先把當(dāng)前圖片往右邊移動(dòng)一步,之前我們說(shuō)的 index * -100% 讓任何一張?jiān)?index 位置的圖片移動(dòng)到當(dāng)前位置的公式,那么要再往右邊移動(dòng)多一個(gè)位置,那就是 (index + 1) * -100% 即可
  • 第二步就是讓下一張圖移動(dòng)到當(dāng)前顯示的位置,這個(gè)就是直接用 index * -100% 咯
  • 最后我們還需要更新一次我們記錄, currentIndex = nextIndex,這樣就大功告成了!

接下來(lái)我們把上面的邏輯翻譯成 JavaScript:

class Carousel extends Component {
 // 構(gòu)造函數(shù)
 // 創(chuàng)建 DOM 節(jié)點(diǎn)
 constructor() {
 super();
 this.attributes = Object.create(null);
 }
 setAttribute(name, value) {
 this.attributes[name] = value;
 }
 render() {
 this.root = document.createElement('div');
 this.root.classList.add('carousel');

 for (let picture of this.attributes.src) {
 let child = document.createElement('div');
 child.style.backgroundImage = `url('${picture}')`;
 this.root.appendChild(child);
 }

 // 當(dāng)前圖片的 index
 let currentIndex = 0;
 setInterval(() => {
 let children = this.root.children;
 // 下一張圖片的 index
 let nextIndex = (currentIndex + 1) % children.length;

 // 當(dāng)前圖片的節(jié)點(diǎn)
 let current = children[currentIndex];
 // 下一張圖片的節(jié)點(diǎn)
 let next = children[nextIndex]; 
	
 // 禁用圖片的動(dòng)效
 next.style.transition = 'none'; 
 // 移動(dòng)下一張圖片到正確的位置
 next.style.transform = `translateX(${-100 * (nextIndex - 1)}%)`;
	
 // 執(zhí)行輪播效果,延遲了一幀的時(shí)間 16 毫秒
 setTimeout(() => {
 // 啟用 CSS 中的動(dòng)效
 next.style.transition = ''; 
 // 先移動(dòng)當(dāng)前圖片離開(kāi)當(dāng)前位置
 current.style.transform = `translateX(${-100 * (currentIndex + 1)}%)`;
 // 移動(dòng)下一張圖片到當(dāng)前顯示的位置
 next.style.transform = `translateX(${-100 * nextIndex}%)`;
		
 // 最后更新當(dāng)前位置的 index
 currentIndex = nextIndex;
 }, 16);
 }, 3000);

 return this.root;
 }
 mountTo(parent) {
 parent.appendChild(this.render());
 }
}

如果我們先去掉 overflow: hidden 的話,我們就可以很清晰的看到所有圖片移動(dòng)的軌跡了:


實(shí)現(xiàn)拖拽輪播

一般來(lái)說(shuō)我們的輪播組件除了這種自動(dòng)輪播的功能之外,還有可以使用我們的鼠標(biāo)進(jìn)行拖動(dòng)來(lái)輪播。所以接下來(lái)我們一起來(lái)實(shí)現(xiàn)這個(gè)手動(dòng)輪播功能。

因?yàn)樽詣?dòng)輪播和手動(dòng)輪播是有一定的沖突的,所以我們需要把我們前面實(shí)現(xiàn)的自動(dòng)輪播的代碼給注釋掉。然后我們就可以使用這個(gè)輪播組件下的 children (子元素),也就是所有圖片的元素,來(lái)實(shí)現(xiàn)我們的手動(dòng)拖拽輪播功能。

那么拖拽的功能主要就是涉及我們的圖片被拖動(dòng),所以我們需要給圖片加入鼠標(biāo)的監(jiān)聽(tīng)事件。如果我們根據(jù)操作步驟來(lái)想的話,就可以整理出這么一套邏輯:

我們肯定是需要先把鼠標(biāo)移動(dòng)到圖片之上,然后點(diǎn)擊圖片。所以我們第一個(gè)需要監(jiān)聽(tīng)的事件必然就是 mousedown 鼠標(biāo)按下事件。點(diǎn)擊了鼠標(biāo)之后,那么我們就會(huì)開(kāi)始移動(dòng)我們的鼠標(biāo),讓我們的圖片跟隨我們鼠標(biāo)移動(dòng)的方向去走。這個(gè)時(shí)候我們就要監(jiān)聽(tīng) mousemove 鼠標(biāo)移動(dòng)事件。當(dāng)我們把圖片拖動(dòng)到我們想要的位置之后,我們就會(huì)松開(kāi)我們鼠標(biāo)的按鍵,這個(gè)時(shí)候也是我們要計(jì)算這個(gè)圖片是否可以輪播的時(shí)候,這個(gè)就需要我們監(jiān)聽(tīng) mouseup 鼠標(biāo)松開(kāi)事件。

this.root.addEventListener('mousedown', event => {
 console.log('mousedown');
});

this.root.addEventListener('mousemove', event => {
 console.log('mousemove');
});

this.root.addEventListener('mouseup', event => {
 console.log('mouseup');
});

執(zhí)行一下以上代碼后,我們就會(huì)在 console 中看到,當(dāng)我們鼠標(biāo)放到圖片上并且移動(dòng)時(shí),我們會(huì)不斷的觸發(fā) mousemove。但是我們想要的效果是,當(dāng)我們鼠標(biāo)按住時(shí)移動(dòng)才會(huì)觸發(fā) mousemove,我們鼠標(biāo)單純?cè)趫D片上移動(dòng)是不應(yīng)該觸發(fā)事件的。

所以我們需要把 mousemove 和 mouseup 兩個(gè)事件,放在 mousedown 事件的回調(diào)函數(shù)當(dāng)中,這樣才能正確的在鼠標(biāo)按住的時(shí)候監(jiān)聽(tīng)移動(dòng)和松開(kāi)兩個(gè)動(dòng)作。這里還需要考慮,當(dāng)我們 mouseup 的時(shí)候,我們需要把 mousemove 和 mouseup 兩個(gè)監(jiān)聽(tīng)事件給停掉,所以我們需要用函數(shù)把它們單獨(dú)的存起來(lái)。

this.root.addEventListener('mousedown', event => {
 console.log('mousedown');

 let move = event => {
 console.log('mousemove');
 };

 let up = event => {
 this.root.removeEventListener('mousemove', move);
 this.root.removeEventListener('mouseup', up);
 };

 this.root.addEventListener('mousemove', move);
 this.root.addEventListener('mouseup', up);
});

這里我們?cè)?mouseup 的時(shí)候就把 mousemove 和 mouseup 的事件給移除了。這個(gè)就是一般我們?cè)谧鐾献У臅r(shí)候都會(huì)用到的基礎(chǔ)代碼。

但是我們又會(huì)發(fā)現(xiàn)另外一個(gè)問(wèn)題,鼠標(biāo)點(diǎn)擊拖動(dòng)然后松開(kāi)后,我們鼠標(biāo)再次在圖片上移動(dòng),還是會(huì)出發(fā)到我們的mousemove 事件。

這個(gè)是因?yàn)槲覀兊?mousemove 是在 root 上被監(jiān)聽(tīng)的。其實(shí)我們的 mousedown 已經(jīng)是在 root 上監(jiān)聽(tīng),我們 mousemove 和 mouseup 就沒(méi)有必要在 root 上監(jiān)聽(tīng)了。

所以我們可以在 document 上直接監(jiān)聽(tīng)這兩個(gè)事件,而在現(xiàn)代瀏覽器當(dāng)中,使用 document 監(jiān)聽(tīng)還有額外的好處,即使我們的鼠標(biāo)移出瀏覽器窗口外我們一樣可以監(jiān)聽(tīng)到事件。

this.root.addEventListener('mousedown', event => {
 console.log('mousedown');

 let move = event => {
 console.log('mousemove');
 };

 let up = event => {
 document.removeEventListener('mousemove', move);
 document.removeEventListener('mouseup', up);
 };

 document.addEventListener('mousemove', move);
 document.addEventListener('mouseup', up);
});

有了這個(gè)完整的監(jiān)聽(tīng)機(jī)制之后,我們就可以嘗試在 mousemove 里面去實(shí)現(xiàn)輪播圖的移動(dòng)功能了。我們一起來(lái)整理一下這個(gè)功能的邏輯:

要做這個(gè)功能,首先我們要知道鼠標(biāo)的位置,這里可以使用 mousemove 中的 event 參數(shù)去捕獲到鼠標(biāo)的坐標(biāo)。event 上其實(shí)有很多個(gè)鼠標(biāo)的坐標(biāo),比如 offsetX、offsetY 等等,這些都是根據(jù)不同的參考系所獲得坐標(biāo)的。在這里我們比較推薦使用的是 clientXclientY這個(gè)坐標(biāo)是相對(duì)于整個(gè)瀏覽器中可渲染區(qū)域的坐標(biāo),它不受任何的因素影響。很多時(shí)候我們組件在瀏覽器這個(gè)容器里面,當(dāng)我們滾動(dòng)了頁(yè)面之后,在一些坐標(biāo)體系中就會(huì)發(fā)生變化。這樣我們就很容易會(huì)出現(xiàn)一些不可調(diào)和的 bug,但是 clientX 和 clientY 就不會(huì)出現(xiàn)這種問(wèn)題。如果要知道我們圖片要往某一個(gè)方向移動(dòng)多少,我們就要知道我們鼠標(biāo)點(diǎn)擊時(shí)的起始坐標(biāo),然后與我們獲取到的 clientX 和 clientY 做對(duì)比。所以我們需要記錄一個(gè) startXstartY,它們的默認(rèn)值就是對(duì)應(yīng)的當(dāng)前 clientX 和 clientY所以我們鼠標(biāo)移動(dòng)的距離就是 終 點(diǎn) 坐 標(biāo) − 起 點(diǎn) 坐 標(biāo) 終點(diǎn)坐標(biāo) - 起點(diǎn)坐標(biāo) 終點(diǎn)坐標(biāo)−起點(diǎn)坐標(biāo),在我們的 move 回調(diào)函數(shù)里面就是 clientX - startXclientY - startY我們輪播圖只支持左右滑動(dòng)的,所以在我們這個(gè)場(chǎng)景中,就不需要 Y 軸的值。那么我們計(jì)算好移動(dòng)距離,就可以給對(duì)應(yīng)被拖動(dòng)的元素加上 transform,這樣圖片就會(huì)被移動(dòng)了我們之前做自動(dòng)輪播的時(shí)候給圖片元素加入了 transition 動(dòng)畫(huà),我們?cè)谕蟿?dòng)的時(shí)候如果有這個(gè)動(dòng)畫(huà),就會(huì)出現(xiàn)延遲一樣的效果,所以在給圖片加入 transform 的同時(shí),我們還需要禁用它們的 transition 屬性

this.root.addEventListener('mousedown', event => {
 let children = this.root.children;
 let startX = event.clientX;

 let move = event => {
 let x = event.clientX - startX;
 for (let child of children) {
 child.style.transition = 'none';
 child.style.transform = `translateX(${x}px)`;
 }
 };

 let up = event => {
 document.removeEventListener('mousemove', move);
 document.removeEventListener('mouseup', up);
 };

 document.addEventListener('mousemove', move);
 document.addEventListener('mouseup', up);
});

好,到了這里我們發(fā)現(xiàn)了兩個(gè)問(wèn)題:

我們第一次點(diǎn)擊然后拖動(dòng)的時(shí)候圖片的起始位置是對(duì)的,但是我們?cè)冱c(diǎn)擊的時(shí)候圖片的位置就不對(duì)了。我們拖動(dòng)了圖片之后,當(dāng)我們松開(kāi)鼠標(biāo)按鈕,這個(gè)圖片就會(huì)停留在拖動(dòng)結(jié)束的位置了,但是在正常的輪播圖組件中,我們?nèi)绻蟿?dòng)了圖片超過(guò)一定的位置,就會(huì)自動(dòng)輪播到下一張圖的。

要解決這兩個(gè)問(wèn)題,我們可以這么計(jì)算,因?yàn)槲覀冏龅氖且粋€(gè)輪播圖的組件,按照現(xiàn)在一般的輪播組件來(lái)說(shuō),當(dāng)我們把圖片拖動(dòng)在大于半個(gè)圖的位置時(shí),就會(huì)輪播到下一張圖了,如果不到一半的位置的話就會(huì)回到當(dāng)前拖動(dòng)的圖的位置。

按照這樣的一個(gè)需求,我們就需要記錄一個(gè) position,它記錄了當(dāng)前是第幾個(gè)圖片(從 0 開(kāi)始計(jì)算)。如果我們每張圖片都是 500px 寬,那么第一張圖的 current 就是 0,偏移的距離就是 0 * 500 = 0, 而第二張圖就是 1 * 500 px,第三張圖就是 2 * 500px,以此類(lèi)推。根據(jù)這樣的規(guī)律,第 N 張圖的偏移位置就是 n ∗ 500 n * 500 n∗500。

首先當(dāng)我們 mousemove 的時(shí)候,我們需要計(jì)算當(dāng)前圖片已經(jīng)從起點(diǎn)移動(dòng)了多遠(yuǎn),這個(gè)就可以通過(guò) N * 500 來(lái)計(jì)算,這里的 N 就是目前的圖片的 position 值。然后我們還需要在 mouseup 的時(shí)候,計(jì)算一下當(dāng)前圖片移動(dòng)的距離是否有超過(guò)半張圖的長(zhǎng)度,如果超過(guò)了,我們直接 transform 到下一張圖的起點(diǎn)位置這里的超出判斷可以使用我們當(dāng)前鼠標(biāo)移動(dòng)的距離 x 除與我們每張圖的 長(zhǎng)度(我們這個(gè)組件控制了圖片是 500px,所以我們就用 x 除與 500),這樣我們就會(huì)得出一個(gè) 0 到 1 的數(shù)字。如果這個(gè)數(shù)字等于或超過(guò) 0.5 那么就是過(guò)了圖一半的長(zhǎng)度了,就可以直接輪播到下一張圖,如果是小于 0.5 就可以移動(dòng)回去當(dāng)前圖的起始位置。上面計(jì)算出來(lái)的值,還可以結(jié)合我們的 position,如果大于等于 0.5 就可以四舍五入變成 1, 否則就是 0。這里的 1 代表我們可以把 position + 1,如果是 0 那么 position 就不會(huì)變。這樣直接改變 current 的值,在 transform 的時(shí)候就會(huì)自動(dòng)按照新的 current 值做計(jì)算,輪播的效果就達(dá)成了。因?yàn)?x 是可以左右移動(dòng)的距離值,也就是說(shuō)如果我們鼠標(biāo)是往左移動(dòng)的話,x 就會(huì)是負(fù)數(shù),而相反就是正數(shù),我們的輪播組件鼠標(biāo)往左拖動(dòng)就是前進(jìn),而往右拖動(dòng)就是回退。所以這里運(yùn)算這個(gè) 超出值 的時(shí)候就是 position = position - Math.round(x/500) 。比如我們鼠標(biāo)往左邊挪動(dòng)了 400px,當(dāng)前 current 值是 0,那么position = 0 - Math.round(400/500) = 0 - -1 = 0 + 1 = 1 所以最后我們的 current 變成了 1。根據(jù)上面的邏輯,我們?cè)?mouseup 的事件中要循環(huán)所有輪播中的 child 圖片,給它們都設(shè)置一個(gè)新的 tranform 值

this.root.addEventListener('mousedown', event => {
 let children = this.root.children;
 let startX = event.clientX;

 let move = event => {
 let x = event.clientX - startX;
 for (let child of children) {
 child.style.transition = 'none';
 child.style.transform = `translateX(${x - current * 500}px)`;
 }
 };

 let up = event => {
 let x = event.clientX - startX;
 current = current - Math.round(x / 500);
 for (let child of children) {
 child.style.transition = '';
 child.style.transform = `translateX(${-current * 500}px)`;
 }
 document.removeEventListener('mousemove', move);
 document.removeEventListener('mouseup', up);
 };

 document.addEventListener('mousemove', move);
 document.addEventListener('mouseup', up);
});

注意這里我們用的 500 作為圖片的長(zhǎng)度,那是因?yàn)槲覀冏约簩?xiě)的圖片組件,它的圖片被我們固定為 500px 寬,而如果我們需要做一個(gè)通用的輪播組件的話,最好就是獲取元素的實(shí)際寬度,Element.clientWith()。這樣我們的組件是可以隨著使用者去改變的。

做到這里,我們就可以用拖拽來(lái)輪播我們的圖片了,但是當(dāng)我們拖到最后一張圖的時(shí)候,我們就會(huì)發(fā)現(xiàn)最后一張圖之后就是空白了,第一張圖沒(méi)有接著最后一張。

那么接下來(lái)我們就去完善這個(gè)功能。這里其實(shí)和我們的自動(dòng)輪播是非常相似的,在做自動(dòng)輪播的時(shí)候我們就知道,每次輪播圖片的時(shí)候,我們最多就只能看到兩張圖片,可以看到三張圖片的機(jī)率是非常小的,因?yàn)槲覀兊妮啿サ膶挾认鄬?duì)我們的頁(yè)面來(lái)說(shuō)是非常小的,除非用戶有足夠的位置去拖到第二張圖以外才會(huì)出現(xiàn)這個(gè)問(wèn)題。但是這里我們就不考慮這種因素了。

我們確定每次拖拽的時(shí)候只會(huì)看到兩張圖片,所以我們也可以像自動(dòng)輪播那樣去處理拖拽的輪播。但是這里有一個(gè)點(diǎn)是不一樣的,我們自動(dòng)輪播的時(shí)候,圖片只會(huì)走一個(gè)方向,要么左要么右邊。但是我們手動(dòng)就可以往左或者往右拖動(dòng),圖片是可以走任意方向的。所以我們就無(wú)法直接用自動(dòng)輪播的代碼來(lái)實(shí)現(xiàn)這個(gè)功能了。我們就需要自己重新處理一下輪播頭和尾無(wú)限循環(huán)的邏輯。

我們可以從 mousemove 的回調(diào)函數(shù)開(kāi)始改造需要找到當(dāng)前元素在屏幕上的位置,我們給它 一個(gè)變量名叫 current,它的值與我們之前在 mouseup 計(jì)算的 position 是一樣的 position + Math.round(x/500)但是當(dāng)前這個(gè)元素是前后都有一張圖,這里我們就不去計(jì)算現(xiàn)在拖動(dòng)是需要拼接它前面還是后面的圖,我們直接就把當(dāng)前元素前后兩個(gè)圖都移動(dòng)到對(duì)應(yīng)的位置即可這里我們直接循環(huán)一個(gè) [-1, 0, 1] 的數(shù)組,對(duì)應(yīng)的是前一個(gè)元素,當(dāng)前元素下一個(gè)元素,這里我們需要使用這三個(gè)偏移值,獲取到上一個(gè)圖片,當(dāng)前拖動(dòng)的圖片和下一個(gè)圖片的移動(dòng)位置,這三個(gè)位置是跟隨著我們鼠標(biāo)的拖動(dòng)實(shí)時(shí)計(jì)算的接著我們?cè)谶@個(gè)循環(huán)里面需要先計(jì)算出前后兩張圖的位置,圖片位置 = 當(dāng)前圖片位置 + 偏移,這里可以這么理解如果當(dāng)前圖片是在 2 這個(gè)位置,上一張圖就是在 1,下一張圖就在 3但是這里有一個(gè)問(wèn)題,如果我們當(dāng)前圖是在 0 的位置,我們上一張圖獲取到的位置就是 -1,按照我們圖片的數(shù)據(jù)結(jié)構(gòu)來(lái)說(shuō),數(shù)組里面是沒(méi)有 -1 這個(gè)位置的。所以當(dāng)我們遇到計(jì)算出來(lái)的位置是負(fù)數(shù)的時(shí)候我們就要把它轉(zhuǎn)成這一列圖片的最后一張圖的位置。按照我們的例子里面的圖片數(shù)據(jù)來(lái)說(shuō)的話,當(dāng)前的圖是在 0 這個(gè)位置,那么上一張圖就應(yīng)該是我們?cè)? 號(hào)位的圖。那么我們?cè)趺茨馨?-1 變成 3, 在結(jié)尾的時(shí)候 4 變成 0 呢?這里需要用到一個(gè)數(shù)學(xué)中的小技巧了,如果我們想讓頭尾的兩個(gè)值超出的時(shí)候可以翻轉(zhuǎn),我們就需要用到一個(gè)公式, 求 (當(dāng)前指針 + 數(shù)組總長(zhǎng)度)/ 數(shù)組總長(zhǎng)度余數(shù),這個(gè)獲得的余數(shù)就正好是翻轉(zhuǎn)的。

我們來(lái)證明一下這個(gè)公式是正確的,首先如果我們遇到 current = 0, 那么 0 這個(gè)位置的圖片的上一張就會(huì)獲得 -1 這個(gè)指針,這個(gè)時(shí)候我們用 ( − 1 + 4 ) / 4 = 3 / 4 (-1 + 4) / 4 = 3 / 4 (−1+4)/4=3/4,這里 3 除以 4 的余數(shù)就是 3,而 3 剛好就是這個(gè)數(shù)組的最后一個(gè)圖片。

然后我們來(lái)試試,如果當(dāng)前圖片就是數(shù)組里面的最后一張圖,在我們的例子里面就是 3,3 + 1 = 4, 這個(gè)時(shí)候通過(guò)轉(zhuǎn)換 ( 4 + 4 ) / 4 (4 + 4) / 4 (4+4)/4 余數(shù)就是 0,顯然我們獲得的數(shù)字就是數(shù)組的第一個(gè)圖片的位置。

通過(guò)這個(gè)公式我們就可以取得上一張和下一張圖片在數(shù)組里面的指針位置,這個(gè)時(shí)候我們就可以用這個(gè)指針獲取到他們?cè)诠?jié)點(diǎn)中的對(duì)象,使用 CSSDOM 來(lái)改變他們的屬性這里我們需要先把所有元素移動(dòng)到當(dāng)前圖片的位置,然后根據(jù) -1、0、1 這三個(gè)偏移的值對(duì)這個(gè)圖片進(jìn)行往左或者往右移動(dòng),最后我們要需要加上當(dāng)前鼠標(biāo)的拖動(dòng)距離

我們已經(jīng)把整個(gè)邏輯給整理了一遍,下來(lái)我們看看 mousemove 這個(gè)事件回調(diào)函數(shù)代碼的應(yīng)該怎么寫(xiě):

let move = event => {
 let x = event.clientX - startX;

 let current = position - Math.round(x / 500);

 for (let offset of [-1, 0, 1]) {
 let pos = current + offset;
 // 計(jì)算圖片所在 index
 pos = (pos + children.length) % children.length;
 console.log('pos', pos);

 children[pos].style.transition = 'none';
 children[pos].style.transform = `translateX(${-pos * 500 + offset * 500 + (x % 500)}px)`;
 }
};

講了那么多東西,代碼就那么幾行,確實(shí)代碼簡(jiǎn)單不等于它背后的邏輯就簡(jiǎn)單。所以寫(xiě)代碼的程序員也可以是深不可測(cè)的。

最后還有一個(gè)小問(wèn)題,在我們拖拽的時(shí)候,我們會(huì)發(fā)現(xiàn)上一張圖和下一張有一個(gè)奇怪跳動(dòng)的現(xiàn)象。

這個(gè)問(wèn)題是我們的 Math.round(x / 500) 所導(dǎo)致的,因?yàn)槲覀冊(cè)?transform 的時(shí)候,加入了 x % 500, 而在我們的 current 值的計(jì)算中沒(méi)有包含這一部分的計(jì)算,所以在鼠標(biāo)拖動(dòng)的時(shí)候就會(huì)缺少這部分的偏移度。

我們只需要把這里的 Math.round(x / 500) 改為 (x - x % 500) / 500 即可達(dá)到同樣的取整數(shù)的效果,同時(shí)還可以保留我們 x 原有的正負(fù)值。

這里其實(shí)還有比較多的問(wèn)題的,我們還沒(méi)有去改 mouseup 事件里面的邏輯。那么接下來(lái)我們就來(lái)看看 up 中的邏輯我們應(yīng)該怎么去實(shí)現(xiàn)。

這里我們需要改的就是 children 中 for 循環(huán)的代碼,我們要實(shí)現(xiàn)的是讓我們拖動(dòng)圖片超過(guò)一定的位置就會(huì)自動(dòng)輪播到對(duì)應(yīng)方向的下一張圖片。up 這里的邏輯其實(shí)是和 move 是基本一樣的,不過(guò)這里有幾個(gè)地方需要更改的:

首先我們的 transition 禁止是可以去掉了,改為 ' ' 空在 transform 中的 + x % 500 就不需要了,因?yàn)檫@里圖片是我們鼠標(biāo)松開(kāi)的時(shí)候,不需要圖片再跟隨我們鼠標(biāo)的位置了在計(jì)算 pos = current + offset的這里,我們?cè)?up 的回調(diào)中是沒(méi)有 current 的,所以我們需要把 current 改為 position因?yàn)橛幸粋€(gè) z-index 的層次關(guān)系,我們會(huì)看到有圖片在被挪動(dòng)位置的時(shí)候,它在我們當(dāng)前圖片上飛過(guò),但是飛過(guò)去的元素其實(shí)是我們不需要的元素,而這個(gè)飛過(guò)去的元素是來(lái)源于我們之前用的 [-1, 0, 1] 這里面的 -1 和 1 的兩個(gè)元素,所以在 up 這個(gè)邏輯里面我們要把不需要的給去掉。意思就是說(shuō),如果我們鼠標(biāo)是往左移動(dòng)的,那么我們只需要 -1 的元素,相反就是只需要 1 的元素,另外的那邊的元素就可以去掉了。首先 for of 循環(huán)是沒(méi)有順序要求的,所以我們可以把 -1 和 1 這兩個(gè)數(shù)字用一個(gè)公式來(lái)代替,放在我們 0 的后面。但是怎么才能找到我們需要的是哪一邊呢?其實(shí)我們需要計(jì)算的就是圖片在移動(dòng)的方向,所以我們要改動(dòng)的就是 position = position - Math.round(x / 500) 這行代碼,這個(gè)方向可以通過(guò) Math.round(x / 500) - x 獲得。而這個(gè)值就是相對(duì)當(dāng)前元素的中間,他是更偏向左邊(負(fù)數(shù))還是右邊(正數(shù)),其實(shí)這個(gè)數(shù)字是多少并不是最重要的,我們要的是它的符號(hào)也就是 -1 還是 1,所以這里我們就可以使用 - Math.sign(Math.round(x / 500) - x) 來(lái)取得結(jié)果中的符號(hào),這個(gè)函數(shù)最終返回要不就是 -1, 要不就是 1 了, 正好是我們想要的。其實(shí)還有一個(gè)小 bug,當(dāng)我們拖動(dòng)當(dāng)前圖片過(guò)短的時(shí)候,圖片位置的計(jì)算是不正確的。

這個(gè)是因?yàn)槲覀兊?Match.round() 的特性,在 250(500px 剛好一半的位置) 之間是有一定的誤區(qū),讓我們無(wú)法判斷圖片需要往那個(gè)方向移動(dòng)的,所以在計(jì)算往 Match.round 的值之后我們還需要加上 + 250 * Match.sign(x),這樣我們的計(jì)算才會(huì)合算出是應(yīng)該往那邊移動(dòng)。

最終我們的代碼就是這樣的:

let up = event => {
 let x = event.clientX - startX;
 position = position - Math.round(x / 500);

 for (let offset of [0, -Math.sign(Math.round(x / 500) - x + 250 * Math.sign(x))]) {
 let pos = position + offset;
 // 計(jì)算圖片所在 index
 pos = (pos + children.length) % children.length;

 children[pos].style.transition = '';
 children[pos].style.transform = `translateX(${-pos * 500 + offset * 500}px)`;
 }

 document.removeEventListener('mousemove', move);
 document.removeEventListener('mouseup', up);
};

改好了 up 函數(shù)之后,我們就真正完成了這個(gè)手動(dòng)輪播的組件了。


到此這篇關(guān)于使用JSX實(shí)現(xiàn)Carousel輪播組件的方法(前端組件化)的文章就介紹到這了,更多相關(guān)JSX實(shí)現(xiàn)Carousel輪播組件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • weex slider實(shí)現(xiàn)滑動(dòng)底部導(dǎo)航功能

    weex slider實(shí)現(xiàn)滑動(dòng)底部導(dǎo)航功能

    這篇文章主要為大家詳細(xì)介紹了weex slider實(shí)現(xiàn)滑動(dòng)底部導(dǎo)航功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-08-08
  • 最新評(píng)論