国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

服務(wù)器之家:專注于服務(wù)器技術(shù)及軟件下載分享
分類導(dǎo)航

node.js|vue.js|jquery|angularjs|React|json|js教程|

服務(wù)器之家 - 編程語(yǔ)言 - JavaScript - js教程 - 使用JSX實(shí)現(xiàn)Carousel輪播組件的方法(前端組件化)

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

2022-02-24 16:30三鉆 js教程

做這個(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í)還不能稱為一個(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)中:

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

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


組件底層封裝

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

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

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

  1. function createElement(type, attributes, ...children) {
  2. // 創(chuàng)建元素
  3. let element;
  4. if (typeof type === 'string') {
  5. element = new ElementWrapper(type);
  6. } else {
  7. element = new type();
  8. }
  9.  
  10. // 掛上屬性
  11. for (let name in attributes) {
  12. element.setAttribute(name, attributes[name]);
  13. }
  14. // 掛上所有子元素
  15. for (let child of children) {
  16. if (typeof child === 'string') child = new TextWrapper(child);
  17. element.appendChild(child);
  18. }
  19. // 最后我們的 element 就是一個(gè)節(jié)點(diǎn)
  20. // 所以我們可以直接返回
  21. return element;
  22. }
  23.  
  24. export class Component {
  25. constructor() {
  26. }
  27. // 掛載元素的屬性
  28. setAttribute(name, attribute) {
  29. this.root.setAttribute(name, attribute);
  30. }
  31. // 掛載元素子元素
  32. appendChild(child) {
  33. child.mountTo(this.root);
  34. }
  35. // 掛載當(dāng)前元素
  36. mountTo(parent) {
  37. parent.appendChild(this.root);
  38. }
  39. }
  40.  
  41. class ElementWrapper extends Component {
  42. // 構(gòu)造函數(shù)
  43. // 創(chuàng)建 DOM 節(jié)點(diǎn)
  44. constructor(type) {
  45. this.root = document.createElement(type);
  46. }
  47. }
  48.  
  49. class TextWrapper extends Component {
  50. // 構(gòu)造函數(shù)
  51. // 創(chuàng)建 DOM 節(jié)點(diǎn)
  52. constructor(content) {
  53. this.root = document.createTextNode(content);
  54. }
  55. }

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

接下來(lái)我們就要繼續(xù)改造我們的 main.js。首先我們需要把 Div 改為 Carousel 并且讓它繼承我們寫好的 Component 父類,這樣我們就可以省略重復(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

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

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

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

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

  1. module.exports = {
  2. entry: './main.js',
  3. mode: 'development',
  4. devServer: {
  5. contentBase: './dist',
  6. },
  7. module: {
  8. rules: [
  9. {
  10. test: /\.js$/,
  11. use: {
  12. loader: 'babel-loader',
  13. options: {
  14. presets: ['@babel/preset-env'],
  15. plugins: [['@babel/plugin-transform-react-jsx', { pragma: 'createElement' }]],
  16. },
  17. },
  18. },
  19. ],
  20. },
  21. };

用過(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" 即可。

  1. {
  2. "name": "jsx-component",
  3. "version": "1.0.0",
  4. "description": "",
  5. "main": "index.js",
  6. "scripts": {
  7. "test": "echo \"Error: no test specified\" && exit 1",
  8. "start": "webpack serve"
  9. },
  10. "author": "",
  11. "license": "ISC",
  12. "devDependencies": {
  13. "@babel/core": "^7.12.3",
  14. "@babel/plugin-transform-react-jsx": "^7.12.5",
  15. "@babel/preset-env": "^7.12.1",
  16. "babel-loader": "^8.1.0",
  17. "webpack": "^5.4.0",
  18. "webpack-cli": "^4.2.0",
  19. "webpack-dev-server": "^3.11.0"
  20. },
  21. "dependencies": {}
  22. }

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

  1. npm start

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

開(kāi)啟了這個(gè)之后,當(dāng)我們修改任何文件時(shí)都會(huì)被監(jiān)聽(tīng)到,這樣就會(huì)實(shí)時(shí)給我們打包文件,非常方便我們調(diào)試。看到上圖里面表示,我們的實(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 的引入路徑。

  1. <!-- main.html 代碼 -->
  2. <body></body>
  3.  
  4. <script src="./main.js"></script>

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

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

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

  1. 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 類的 constructor 中先初始化這個(gè)類屬性。

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

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

  1. class Carousel extends Component {
  2. // 構(gòu)造函數(shù)
  3. // 創(chuàng)建 DOM 節(jié)點(diǎn)
  4. constructor() {
  5. super();
  6. this.attributes = Object.create(null);
  7. }
  8. setAttribute(name, value) {
  9. this.attributes[name] = value;
  10. }
  11. render() {
  12. console.log(this.attributes);
  13. return document.createElement('div');
  14. }
  15. mountTo() {
  16. parent.appendChild(this.render());
  17. }
  18. }

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

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

接下來(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
  1. class Carousel extends Component {
  2. // 構(gòu)造函數(shù)
  3. // 創(chuàng)建 DOM 節(jié)點(diǎn)
  4. constructor() {
  5. super();
  6. this.attributes = Object.create(null);
  7. }
  8. setAttribute(name, value) {
  9. this.attributes[name] = value;
  10. }
  11. render() {
  12. this.root = document.createElement('div');
  13.  
  14. for (let picture of this.attributes.src) {
  15. let child = document.createElement('img');
  16. child.src = picture;
  17. this.root.appendChild(child);
  18. }
  19.  
  20. return this.root;
  21. }
  22. mountTo(parent) {
  23. parent.appendChild(this.render());
  24. }
  25. }

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

就這樣我們就可以看到我們的圖片被正確的顯示在我們的頁(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,然后給他們合適的樣式。

  1. // main.js
  2. class Carousel extends Component {
  3. // 構(gòu)造函數(shù)
  4. // 創(chuàng)建 DOM 節(jié)點(diǎn)
  5. constructor() {
  6. super();
  7. this.attributes = Object.create(null);
  8. }
  9. setAttribute(name, value) {
  10. this.attributes[name] = value;
  11. }
  12. render() {
  13. this.root = document.createElement('div');
  14. this.root.addClassList('carousel'); // 加入 carousel class
  15.  
  16. for (let picture of this.attributes.src) {
  17. let child = document.createElement('div');
  18. child.backgroundImage = `url('${picture}')`;
  19. this.root.appendChild(child);
  20. }
  21.  
  22. return this.root;
  23. }
  24. mountTo(parent) {
  25. parent.appendChild(this.render());
  26. }
  27. }
  1. <!-- main.html -->
  2. <head>
  3. <style>
  4. .carousel > div {
  5. width: 500px;
  6. height: 281px;
  7. background-size: contain;
  8. }
  9. </style>
  10. </head>
  11.  
  12. <body></body>
  13.  
  14. <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 里面了。

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

一個(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。這樣我們就大功告成了。

  1. <head>
  2. <style>
  3. .carousel {
  4. width: 500px;
  5. height: 281px;
  6. white-space: nowrap;
  7. overflow: hidden;
  8. }
  9.  
  10. .carousel > div {
  11. width: 500px;
  12. height: 281px;
  13. background-size: contain;
  14. display: inline-block;
  15. }
  16. </style>
  17. </head>
  18.  
  19. <body></body>
  20.  
  21. <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 是最符合人類的感覺(jué)的一種運(yùn)動(dòng)曲線。

  1. <head>
  2. <style>
  3. .carousel {
  4. width: 500px;
  5. height: 281px;
  6. white-space: nowrap;
  7. overflow: hidden;
  8. }
  9.  
  10. .carousel > div {
  11. width: 500px;
  12. height: 281px;
  13. background-size: contain;
  14. display: inline-block;
  15. transition: ease 0.5s;
  16. }
  17. </style>
  18. </head>
  19.  
  20. <body></body>
  21.  
  22. <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)中專門用于挪動(dòng)元素的。所以這里我們的邏輯就是,每 3 秒往左邊挪動(dòng)一次元素自身的長(zhǎng)度,這樣我們就可以挪動(dòng)到下一張圖的開(kāi)始。

但是這樣只能挪動(dòng)一張圖,所以如果我們需要挪動(dòng)第二次,到達(dá)第三張圖,我們就要讓每一張圖偏移 200%,以此類推。所以我們需要一個(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),一張一張圖片展示了。

  1. class Carousel extends Component {
  2. // 構(gòu)造函數(shù)
  3. // 創(chuàng)建 DOM 節(jié)點(diǎn)
  4. constructor() {
  5. super();
  6. this.attributes = Object.create(null);
  7. }
  8. setAttribute(name, value) {
  9. this.attributes[name] = value;
  10. }
  11. render() {
  12. this.root = document.createElement('div');
  13. this.root.classList.add('carousel');
  14.  
  15. for (let picture of this.attributes.src) {
  16. let child = document.createElement('div');
  17. child.style.backgroundImage = `url('${picture}')`;
  18. this.root.appendChild(child);
  19. }
  20.  
  21. let current = 0;
  22. setInterval(() => {
  23. let children = this.root.children;
  24. ++current;
  25. for (let child of children) {
  26. child.style.transform = `translateX(-${100 * current}%)`;
  27. }
  28. }, 3000);
  29.  
  30. return this.root;
  31. }
  32. mountTo(parent) {
  33. parent.appendChild(this.render());
  34. }
  35. }

這里我們發(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è)刂校琧hildren 的長(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:

  1. class Carousel extends Component {
  2. // 構(gòu)造函數(shù)
  3. // 創(chuàng)建 DOM 節(jié)點(diǎn)
  4. constructor() {
  5. super();
  6. this.attributes = Object.create(null);
  7. }
  8. setAttribute(name, value) {
  9. this.attributes[name] = value;
  10. }
  11. render() {
  12. this.root = document.createElement('div');
  13. this.root.classList.add('carousel');
  14.  
  15. for (let picture of this.attributes.src) {
  16. let child = document.createElement('div');
  17. child.style.backgroundImage = `url('${picture}')`;
  18. this.root.appendChild(child);
  19. }
  20.  
  21. // 當(dāng)前圖片的 index
  22. let currentIndex = 0;
  23. setInterval(() => {
  24. let children = this.root.children;
  25. // 下一張圖片的 index
  26. let nextIndex = (currentIndex + 1) % children.length;
  27.  
  28. // 當(dāng)前圖片的節(jié)點(diǎn)
  29. let current = children[currentIndex];
  30. // 下一張圖片的節(jié)點(diǎn)
  31. let next = children[nextIndex];
  32.  
  33. // 禁用圖片的動(dòng)效
  34. next.style.transition = 'none';
  35. // 移動(dòng)下一張圖片到正確的位置
  36. next.style.transform = `translateX(${-100 * (nextIndex - 1)}%)`;
  37.  
  38. // 執(zhí)行輪播效果,延遲了一幀的時(shí)間 16 毫秒
  39. setTimeout(() => {
  40. // 啟用 CSS 中的動(dòng)效
  41. next.style.transition = '';
  42. // 先移動(dòng)當(dāng)前圖片離開(kāi)當(dāng)前位置
  43. current.style.transform = `translateX(${-100 * (currentIndex + 1)}%)`;
  44. // 移動(dòng)下一張圖片到當(dāng)前顯示的位置
  45. next.style.transform = `translateX(${-100 * nextIndex}%)`;
  46.  
  47. // 最后更新當(dāng)前位置的 index
  48. currentIndex = nextIndex;
  49. }, 16);
  50. }, 3000);
  51.  
  52. return this.root;
  53. }
  54. mountTo(parent) {
  55. parent.appendChild(this.render());
  56. }
  57. }

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

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


實(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)事件。

  1. this.root.addEventListener('mousedown', event => {
  2. console.log('mousedown');
  3. });
  4.  
  5. this.root.addEventListener('mousemove', event => {
  6. console.log('mousemove');
  7. });
  8.  
  9. this.root.addEventListener('mouseup', event => {
  10. console.log('mouseup');
  11. });

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

執(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)。

  1. this.root.addEventListener('mousedown', event => {
  2. console.log('mousedown');
  3.  
  4. let move = event => {
  5. console.log('mousemove');
  6. };
  7.  
  8. let up = event => {
  9. this.root.removeEventListener('mousemove', move);
  10. this.root.removeEventListener('mouseup', up);
  11. };
  12.  
  13. this.root.addEventListener('mousemove', move);
  14. this.root.addEventListener('mouseup', up);
  15. });

這里我們?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)到事件。

  1. this.root.addEventListener('mousedown', event => {
  2. console.log('mousedown');
  3.  
  4. let move = event => {
  5. console.log('mousemove');
  6. };
  7.  
  8. let up = event => {
  9. document.removeEventListener('mousemove', move);
  10. document.removeEventListener('mouseup', up);
  11. };
  12.  
  13. document.addEventListener('mousemove', move);
  14. document.addEventListener('mouseup', up);
  15. });

有了這個(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),比如 offsetXoffsetY 等等,這些都是根據(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 屬性

  1. this.root.addEventListener('mousedown', event => {
  2. let children = this.root.children;
  3. let startX = event.clientX;
  4.  
  5. let move = event => {
  6. let x = event.clientX - startX;
  7. for (let child of children) {
  8. child.style.transition = 'none';
  9. child.style.transform = `translateX(${x}px)`;
  10. }
  11. };
  12.  
  13. let up = event => {
  14. document.removeEventListener('mousemove', move);
  15. document.removeEventListener('mouseup', up);
  16. };
  17.  
  18. document.addEventListener('mousemove', move);
  19. document.addEventListener('mouseup', up);
  20. });

好,到了這里我們發(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,以此類推。根據(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 值

  1. this.root.addEventListener('mousedown', event => {
  2. let children = this.root.children;
  3. let startX = event.clientX;
  4.  
  5. let move = event => {
  6. let x = event.clientX - startX;
  7. for (let child of children) {
  8. child.style.transition = 'none';
  9. child.style.transform = `translateX(${x - current * 500}px)`;
  10. }
  11. };
  12.  
  13. let up = event => {
  14. let x = event.clientX - startX;
  15. current = current - Math.round(x / 500);
  16. for (let child of children) {
  17. child.style.transition = '';
  18. child.style.transform = `translateX(${-current * 500}px)`;
  19. }
  20. document.removeEventListener('mousemove', move);
  21. document.removeEventListener('mouseup', up);
  22. };
  23.  
  24. document.addEventListener('mousemove', move);
  25. document.addEventListener('mouseup', up);
  26. });

注意這里我們用的 500 作為圖片的長(zhǎng)度,那是因?yàn)槲覀冏约簩懙膱D片組件,它的圖片被我們固定為 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)該怎么寫:

  1. let move = event => {
  2. let x = event.clientX - startX;
  3.  
  4. let current = position - Math.round(x / 500);
  5.  
  6. for (let offset of [-1, 0, 1]) {
  7. let pos = current + offset;
  8. // 計(jì)算圖片所在 index
  9. pos = (pos + children.length) % children.length;
  10. console.log('pos', pos);
  11.  
  12. children[pos].style.transition = 'none';
  13. children[pos].style.transform = `translateX(${-pos * 500 + offset * 500 + (x % 500)}px)`;
  14. }
  15. };

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

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

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

這個(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ì)算是不正確的。

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

這個(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)。

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

  1. let up = event => {
  2. let x = event.clientX - startX;
  3. position = position - Math.round(x / 500);
  4.  
  5. for (let offset of [0, -Math.sign(Math.round(x / 500) - x + 250 * Math.sign(x))]) {
  6. let pos = position + offset;
  7. // 計(jì)算圖片所在 index
  8. pos = (pos + children.length) % children.length;
  9.  
  10. children[pos].style.transition = '';
  11. children[pos].style.transform = `translateX(${-pos * 500 + offset * 500}px)`;
  12. }
  13.  
  14. document.removeEventListener('mousemove', move);
  15. document.removeEventListener('mouseup', up);
  16. };

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

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


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

 

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

原文鏈接:https://tridiamond.blog.csdn.net/article/details/112656150

延伸 · 閱讀

精彩推薦
  • js教程JavaScript實(shí)現(xiàn)H5接金幣功能(實(shí)例代碼)

    JavaScript實(shí)現(xiàn)H5接金幣功能(實(shí)例代碼)

    這篇文章主要介紹了JavaScript實(shí)現(xiàn)H5接金幣功能,本文分步驟通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友...

    AloneSundy4292022-01-22
  • js教程Selenium執(zhí)行JavaScript腳本的方法示例

    Selenium執(zhí)行JavaScript腳本的方法示例

    這篇文章主要介紹了Selenium執(zhí)行JavaScript腳本的方法示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友...

    測(cè)試開(kāi)發(fā)小記6532021-12-23
  • js教程JavaScript快速實(shí)現(xiàn)日歷效果

    JavaScript快速實(shí)現(xiàn)日歷效果

    這篇文章主要為大家詳細(xì)介紹了JavaScript快速實(shí)現(xiàn)日歷效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下...

    云杰8了10832022-02-13
  • js教程JavaScript實(shí)現(xiàn)頁(yè)面動(dòng)態(tài)驗(yàn)證碼的實(shí)現(xiàn)示例

    JavaScript實(shí)現(xiàn)頁(yè)面動(dòng)態(tài)驗(yàn)證碼的實(shí)現(xiàn)示例

    這篇文章主要介紹了JavaScript實(shí)現(xiàn)頁(yè)面動(dòng)態(tài)驗(yàn)證碼的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的...

    人生行者6642022-02-20
  • js教程原生js實(shí)現(xiàn)滑塊區(qū)間組件

    原生js實(shí)現(xiàn)滑塊區(qū)間組件

    這篇文章主要為大家詳細(xì)介紹了js實(shí)現(xiàn)滑塊區(qū)間組件,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下...

    蒲公英芽12072022-01-05
  • js教程JavaScript實(shí)現(xiàn)鼠標(biāo)拖拽調(diào)整div大小

    JavaScript實(shí)現(xiàn)鼠標(biāo)拖拽調(diào)整div大小

    這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)鼠標(biāo)拖拽調(diào)整div大小,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下...

    BDawn9192022-02-12
  • js教程JavaScript 繪制餅圖的示例

    JavaScript 繪制餅圖的示例

    這篇文章主要介紹了JavaScript 繪制餅圖的示例,幫助大家更好的利用JavaScript繪制圖表,感興趣的朋友可以了解下...

    MwqgKG11432022-01-21
  • js教程確保JavaScript 安全的五大做法

    確保JavaScript 安全的五大做法

    如果你運(yùn)行交互式網(wǎng)站或應(yīng)用程序,JavaScript 安全性是重中之重。 從程序錯(cuò)誤和不安全的用戶輸入到惡意攻擊,有很多事情可能會(huì)出錯(cuò)。...

    粵嵌教育8782022-01-11
主站蜘蛛池模板: 自拍偷拍亚洲欧美 | 三级网站在线播放 | 亚洲精品欧美 | 韩国精品免费视频 | 91麻豆精品国产91久久久资源速度 | 国产最新一区 | 免费裸体视频网站 | 国产免费爽爽视频在线观看 | 亚洲天堂久久 | 成人国产精品久久 | 国产v日产∨综合v精品视频 | 国产精品美女高潮无套久久 | 91色视频在线观看 | 天天操,夜夜操 | 人人澡人人射 | 91丁香婷婷综合久久欧美 | 一本一本久久a久久精品综合妖精 | 欧美日韩a| 在线日韩欧美 | 日韩一区欧美 | 91精品国产综合久久久久久 | 日韩av免费在线观看 | 国产精品日韩欧美 | 国产精品国产成人国产三级 | 亚洲自拍偷拍综合 | 99精品电影 | 精品成人免费一区二区在线播放 | 色婷婷综合久久久中文字幕 | 日日操天天爽 | 国产精品久久久久久久久久久久冷 | 国产精品久久久久久久久久久小说 | 久久99精品久久久久久国产越南 | 国产精品福利在线 | 国产精品美女久久久久久久久久久 | 亚洲av毛片 | 婷婷久久综合 | 国产一区二区三区四区hd | 国产91精品久久久久 | av中文字幕免费在线观看 | 极品国产精品 | 亚洲大片 |