element 源碼學習六 —— Carousel 走馬燈學習

簡單使用

走馬燈功能在展現圖片時常常用到,而 element 中提供了 Carousel 組件。出於好奇學習一下它的實現原理。
具體的功能詳情請查閱官方文檔
關於組件屬性,該組件提供了組件高度、索引、指示器、切換時間等一衆配置,這個只要動手試一遍都能理解。
關於事件,提供了一個 change 事件。能夠經過 v-on:change="changeFun" 事件綁定來監聽。該事件傳遞了兩個參數:當前頁索引和前一頁索引。參考源碼中的 $emit 方法:css

watch: {
    activeIndex(val, oldVal) {
      this.resetItemPosition(oldVal);
      this.$emit('change', val, oldVal);
    },
  },

關於方法,組件提供了三個方法用於操做組件頁面的切換。使用方法是經過 $ref 子組件引用來訪問子組件,執行其方法。html

// 假設爲 el-carousel 設置了 ref="car"
                // setActiveItem    手動切換幻燈片    須要切換的幻燈片的索引,從 0 開始;或相應 el-carousel-item 的 name 屬性值
                // prev    切換至上一張幻燈片
                // next 切換至下一張幻燈片
                pre() {
                    this.$refs.car.prev()
                },
                next() {
                    this.$refs.car.next()
                },
                first() {
                    this.$refs.car.setActiveItem(0)
                }

源碼位置

Carousel 的源碼位於 package/carousel/ 目錄下。源碼目錄以下:
源碼目錄vue

簡單說下目錄內容:git

  • item.vue carousel-item 組件代碼
  • main.vue carousel 組件代碼
  • _index.js 導出 carousel-item 和 carousel
  • cooking.conf.js cooking 配置
  • index.js 導出 carousel 組件
  • package.json 組件信息
  • index.js 導出 carousel-item 組件

其實主要就是兩個 .vue 文件,其餘都是些配置、導出的功能文件,這裏能夠忽略不看~json

源碼解析

老規矩,咱們經過幾個問題切入去看源代碼。ide

1. 基本原理:頁面切換 + 指示器 + 切換按鈕的實現

頁面切換

看了一下 DOM 的 Elements 排列,發現頁面切換使用的是 transform 2D 轉換和 transition 過渡。
通常頁面切換實際上是幾個頁面使用 translateX 進行位移切換。
通常頁面結構學習

而卡片變化切換同時使用了 translateX 位移和 scale 縮放實現中間一張卡最大,左右卡片小的效果的。
卡片化動畫

而卡片的層疊使用 z-index 值得大小來實現。
因此,邏輯計算只須要根據當前顯示頁面計算下每一個頁面的位移值。而 carousel-item 中也的確有計算的邏輯:ui

// item.vue
    // index 當前 item 索引
    // activeIndex 激活的 item 索引
    // oldIndex 以前 item 索引
    translateItem(index, activeIndex, oldIndex) {
      // 獲取父元素寬度 https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetWidth
      const parentWidth = this.$parent.$el.offsetWidth;
      // 獲取 item 頁面數量
      const length = this.$parent.items.length;
      // 判斷是否須要過渡動畫 class
      // .el-carousel__item.is-animating {
      //  transition: transform 0.4s ease-in-out;
      // }
      if (this.$parent.type !== "card" && oldIndex !== undefined) {
        this.animating = index === activeIndex || index === oldIndex;
      }
      // 處理 index
      if (index !== activeIndex && length > 2) {
        index = this.processIndex(index, activeIndex, length);
      }
      if (this.$parent.type === "card") {
        // 卡片化
        this.inStage = Math.round(Math.abs(index - activeIndex)) <= 1; // 激活組件及其先後組件定義 cursor: pointer; z-index: 1;
        this.active = index === activeIndex; // 激活 class
        // 計算卡片化偏移量
        this.translate = this.calculateTranslate(
          index,
          activeIndex,
          parentWidth
        );
        // 激活卡片不縮放,其餘卡片縮放爲 0.83
        this.scale = this.active ? 1 : CARD_SCALE;
      } else {
        // 非卡片化
        this.active = index === activeIndex; // 激活 class
        this.translate = parentWidth * (index - activeIndex); // 計算位移 根據父組件寬度計算
      }
      // 計算完後顯示
      this.ready = true;
    },

指示器

指示器就是多個 button 組成的橫向列表,根據當前顯示頁面修改某個指示器背景顏色。this

<ul
      class="el-carousel__indicators"
      v-if="indicatorPosition !== 'none'"
      :class="{ 'el-carousel__indicators--labels': hasLabel, 'el-carousel__indicators--outside': indicatorPosition === 'outside' || type === 'card' }">
      <li
        v-for="(item, index) in items"
        class="el-carousel__indicator"
        :class="{ 'is-active': index === activeIndex }"
        @mouseenter="throttledIndicatorHover(index)"
        @click.stop="handleIndicatorClick(index)">
        <button class="el-carousel__button"><span v-if="hasLabel">{{ item.label }}</span></button>
      </li>

切換按鈕

切換按鈕其實就是兩個 absolute 的按鈕,點擊實現頁面切換便可。

<transition name="carousel-arrow-left">
        <button
          type="button"
          v-if="arrow !== 'never'"
          v-show="arrow === 'always' || hover"
          @mouseenter="handleButtonEnter('left')"
          @mouseleave="handleButtonLeave"
          @click.stop="throttledArrowClick(activeIndex - 1)"
          class="el-carousel__arrow el-carousel__arrow--left">
          <i class="el-icon-arrow-left"></i>
        </button>
      </transition>
      <transition name="carousel-arrow-right">
        <button
          type="button"
          v-if="arrow !== 'never'"
          v-show="arrow === 'always' || hover"
          @mouseenter="handleButtonEnter('right')"
          @mouseleave="handleButtonLeave"
          @click.stop="throttledArrowClick(activeIndex + 1)"
          class="el-carousel__arrow el-carousel__arrow--right">
          <i class="el-icon-arrow-right"></i>
        </button>
      </transition>

這裏要注意的就是按鈕外包裹了 <transition> 標籤來實現按鈕進入和離開的過渡效果。

2. 頁面切換動畫如何實現?

其實在基本原理裏面都提到了,能夠從源碼中找到兩個樣式:

# 內聯樣式
element.style {
  transform: translateX(888.56px) scale(0.83);
}

# 過渡樣式
.is-animating {
  transition: transform 0.4s ease-in-out;
}

靜態狀況下,使用 transitionX 維持頁面位置,顯示當前頁面。過渡行爲時,加入 0.4 秒的 transition 過渡效果。

3. 卡片化如何實現效果?

卡片化切換其實和通常的切換差很少,只是顯示卡片從一張變爲三張。中間的卡片 z-index 爲 2,而左右的卡片 z-index 爲 1,從而實現中間卡片在前面的樣式。而計算三張卡片位置的方法以下:

calculateTranslate(index, activeIndex, parentWidth) {
      if (this.inStage) {
        return parentWidth * ((2 - CARD_SCALE) * (index - activeIndex) + 1) / 4;
      } else if (index < activeIndex) {
        return -(1 + CARD_SCALE) * parentWidth / 4;
      } else {
        return (3 + CARD_SCALE) * parentWidth / 4;
      }
    },

而卡片的寬度爲容器寬度的一半~左右兩邊的卡片縮放了 83% 。

.el-carousel__item--card {
  width: 50%;
  transition: transform 0.4s ease-in-out;
}

element.style {
  transform: translateX(-39.44px) scale(0.83)
}

總結下:

  • 顯示三張卡片。
  • 三張卡片高度跟隨容器,寬度爲容器的 50%。
  • 左右兩張卡片縮放了 83% 大小。
  • 左右兩張卡片 z-index 爲 1;中間卡片 z-index 爲 2。

4. 按鈕出現和消失的效果如何實現?

使用了 vue 的 transition 標籤來實現。具體效果爲:

.el-carousel__arrow {
  border: none;
  outline: none;
  padding: 0;
  margin: 0;
  height: 36px;
  width: 36px;
  cursor: pointer;
  transition: 0.3s;
  border-radius: 50%;
  background-color: rgba(31, 45, 61, 0.11);
  color: #fff;
  position: absolute;
  top: 50%;
  z-index: 10;
  transform: translateY(-50%);
  text-align: center;
  font-size: 12px;
}

.carousel-arrow-left-enter,
.carousel-arrow-left-leave-active {
  transform: translateY(-50%) translateX(-10px);
  opacity: 0;
}

.carousel-arrow-right-enter,
.carousel-arrow-right-leave-active {
  transform: translateY(-50%) translateX(10px);
  opacity: 0;
}

實現的效果爲:左邊箭頭向從左邊 10px 位置進入和離開,而右邊箭頭從右邊 10px 位置進入和離開。而 translateY 是爲了垂直居中。

5. 如何實現自動切換功能?

使用 setInterval 方法來實現定時向後切換頁面。

playSlides() {
      if (this.activeIndex < this.items.length - 1) {
        this.activeIndex++;
      } else {
        // 若是是最後一頁則跳轉到第一頁
        this.activeIndex = 0;
      }
    },
    // 取消 timer
    pauseTimer() {
      clearInterval(this.timer);
    },
    // 開始 timer
    startTimer() {
      if (this.interval <= 0 || !this.autoplay) return;
      this.timer = setInterval(this.playSlides, this.interval);
    },

本身實現個走馬燈玩玩~

學以至用,這裏寫個簡單的 demo,實現下走馬燈功能。
例一:走馬燈效果
例二:卡片走馬燈效果
其中,例一實現了自動切換、手動切換、切換效果。例二僅實現了卡片化的效果,具體邏輯計算麻煩沒有去搞。
這裏留了幾個問題待解決:

  • 卡片化的後面的卡片的計算邏輯是如何的?
  • 走馬燈中第一頁和最後一頁的切換如何作到更好?

這些問題將會盡快解決!

最後

至此,咱們瞭解了走馬燈的實現原理,以及一些小功能的實現。最後也用兩個例子證實了咱們的分析。

Vue 實驗室

《Vue 實驗室》 至今也寫了很多了,做者將在把最後幾篇計劃中的博客寫完後,以當前的知識認知水平從新改進《Vue 實驗室》往期的文章,並整理到 gitbook 中便於讀者閱讀,敬請期待。

相關文章
相關標籤/搜索