Carousel 走馬燈源碼分析整理筆記,這篇寫的不詳細,後面有空補充html
<template> <!--走馬燈的最外層包裹div--> <div class="el-carousel" :class="{ 'el-carousel--card': type === 'card' }" @mouseenter.stop="handleMouseEnter" @mouseleave.stop="handleMouseLeave"> <div class="el-carousel__container" :style="{ height: height }"> <!--左邊的切換箭頭--> <transition name="carousel-arrow-left"> <button type="button" v-if="arrow !== 'never'" v-show="(arrow === 'always' || hover) && (loop || activeIndex > 0)" @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) && (loop || activeIndex < items.length - 1)" @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> <!--幻燈片內容顯示區域--> <slot></slot> </div> <!--底部的指示器列表,點擊或hover時切換幻燈片--> <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> </ul> </div> </template> <script> //throttle節流函數 import throttle from 'throttle-debounce/throttle'; import { addResizeListener, removeResizeListener } from 'element-ui/src/utils/resize-event'; export default { name: 'ElCarousel', props: { initialIndex: { //初始狀態激活的幻燈片的索引,從 0 開始 type: Number, default: 0 }, height: String, //走馬燈的高度 trigger: { //指示器的觸發方式 type: String, default: 'hover' }, autoplay: { //是否自動切換 type: Boolean, default: true }, interval: { //自動切換的時間間隔,單位爲毫秒 type: Number, default: 3000 }, indicatorPosition: String, //指示器的位置 indicator: { type: Boolean, default: true }, arrow: { //切換箭頭的顯示時機 always/hover/never type: String, default: 'hover' }, type: String, //走馬燈的類型,card loop: { //是否循環顯示 type: Boolean, default: true } }, data() { return { items: [], //幻燈片數組 activeIndex: -1, //標識當前幻燈片索引 containerWidth: 0, timer: null, hover: false //記錄當前鼠標的移入狀態 }; }, computed: { hasLabel() { return this.items.some(item => item.label.toString().length > 0); } }, watch: { items(val) { if (val.length > 0) this.setActiveItem(this.initialIndex); }, activeIndex(val, oldVal) { this.resetItemPosition(oldVal); this.$emit('change', val, oldVal); }, autoplay(val) { val ? this.startTimer() : this.pauseTimer(); }, loop() { this.setActiveItem(this.activeIndex); } }, methods: { // 當鼠標移入 handleMouseEnter() { // 當鼠標移入時,清空幻燈片播放的定時器,暫停自動切換。 this.hover = true; this.pauseTimer(); }, // 當鼠標移出 handleMouseLeave() { // 當鼠標移出,設置幻燈片自動播放定時器 this.hover = false; this.startTimer(); }, itemInStage(item, index) { const length = this.items.length; // 知足當前爲最後一個幻燈片;當前幻燈片在場景內;第一個幻燈片激活狀態; // 或者 知足 當前幻燈片在場景內;當前幻燈片後面有至少一個項目;當前幻燈片後面一個項目處於激活狀態 if (index === length - 1 && item.inStage && this.items[0].active || (item.inStage && this.items[index + 1] && this.items[index + 1].active)) { return 'left'; } else if (index === 0 && item.inStage && this.items[length - 1].active || (item.inStage && this.items[index - 1] && this.items[index - 1].active)) { return 'right'; } return false; }, // 當鼠標移入左邊的切換幻燈片的按鈕 handleButtonEnter(arrow) { this.items.forEach((item, index) => { if (arrow === this.itemInStage(item, index)) { item.hover = true; } }); }, handleButtonLeave() { this.items.forEach(item => { item.hover = false; }); }, // 將全部的幻燈片放入items數組中 updateItems() { this.items = this.$children.filter(child => child.$options.name === 'ElCarouselItem'); }, // 重置幻燈片位置 resetItemPosition(oldIndex) { this.items.forEach((item, index) => { item.translateItem(index, this.activeIndex, oldIndex); }); }, //改變當前的幻燈片 playSlides() { if (this.activeIndex < this.items.length - 1) { this.activeIndex++; } else if (this.loop) { this.activeIndex = 0; } }, pauseTimer() { // 清空定時器 clearInterval(this.timer); }, startTimer() { // 若是自動切換的時間間隔小於等於0時,或者用戶未設置自動播放時,直接返回,幻燈片不自動播放 if (this.interval <= 0 || !this.autoplay) return; this.timer = setInterval(this.playSlides, this.interval); }, //設置當前頁 setActiveItem(index) { // 若是index是字符串,則是用戶設置了幻燈片的name if (typeof index === 'string') { // 找到對應name的幻燈片 const filteredItems = this.items.filter(item => item.name === index); // 若是找到的items長度大於0,取第一個的索引做爲咱們要使用的索引 if (filteredItems.length > 0) { index = this.items.indexOf(filteredItems[0]); } } // 索引轉成數字 index = Number(index); // 若是索引不是數字,或者不是整數 if (isNaN(index) || index !== Math.floor(index)) { // 若是不是生產環境下,就報warn process.env.NODE_ENV !== 'production' && console.warn('[Element Warn][Carousel]index must be an integer.'); return; } // 獲取幻燈片數組的長度 let length = this.items.length; const oldIndex = this.activeIndex; // 若是索引小於0,判斷是否設置循環播放,若是設置了,設置當前頁爲最後一頁;也就是在向前切換到第一張,繼續向前切換顯示最後一張,而後顯示倒數第二張 if (index < 0) { this.activeIndex = this.loop ? length - 1 : 0; } else if (index >= length) { //若是索引大於數組長度,判斷是否設置循環播放,若是設置了,設置當前頁爲第一頁;也就是在向後切換到最後一張時,繼續向後切換顯示第一張,而後顯示第二張 this.activeIndex = this.loop ? 0 : length - 1; } else { //不然,當前頁設置爲索引頁 this.activeIndex = index; } if (oldIndex === this.activeIndex) { this.resetItemPosition(oldIndex); } }, prev() { this.setActiveItem(this.activeIndex - 1); }, next() { this.setActiveItem(this.activeIndex + 1); }, handleIndicatorClick(index) { this.activeIndex = index; }, handleIndicatorHover(index) { if (this.trigger === 'hover' && index !== this.activeIndex) { this.activeIndex = index; } } }, created() { // throttle節流函數,點擊頻率控制,返回函數連續調用時 http://npm.taobao.org/package/throttle-debounce // 第二個參數noTrailing,當其設置爲true時,保證函數每隔delay時間只能執行一次,若是設置爲false或者沒有指定,則會在最後一次函數調用後的delay時間後重置計時器。 this.throttledArrowClick = throttle(300, true, index => { this.setActiveItem(index); }); this.throttledIndicatorHover = throttle(300, index => { this.handleIndicatorHover(index); }); }, mounted() { this.updateItems(); this.$nextTick(() => { addResizeListener(this.$el, this.resetItemPosition); if (this.initialIndex < this.items.length && this.initialIndex >= 0) { this.activeIndex = this.initialIndex; } this.startTimer(); }); }, beforeDestroy() { if (this.$el) removeResizeListener(this.$el, this.resetItemPosition); } }; </script>
<template> <!--單個的幻燈片html結構--> <div v-show="ready" class="el-carousel__item" :class="{ 'is-active': active, 'el-carousel__item--card': $parent.type === 'card', 'is-in-stage': inStage, 'is-hover': hover, 'is-animating': animating }" @click="handleItemClick" :style="{ msTransform: `translateX(${ translate }px) scale(${ scale })`, webkitTransform: `translateX(${ translate }px) scale(${ scale })`, transform: `translateX(${ translate }px) scale(${ scale })` }"> <div v-if="$parent.type === 'card'" v-show="!active" class="el-carousel__mask"></div> <!--幻燈片的自定義內容以插槽的方式顯示在此區域--> <slot></slot> </div> </template> <script> const CARD_SCALE = 0.83; export default { name: 'ElCarouselItem', props: { name: String, //幻燈片的名字,可用做 setActiveItem 的參數 label: { //該幻燈片所對應指示器的文本 type: [String, Number], default: '' } }, data() { return { hover: false, translate: 0, //偏移量設置 scale: 1, active: false, ready: false, inStage: false, animating: false }; }, methods: { processIndex(index, activeIndex, length) { if (activeIndex === 0 && index === length - 1) { //當前是activeIndex是第一張,index是最後一張,返回-1,相差-1,表示兩者相鄰且在左側 return -1; } else if (activeIndex === length - 1 && index === 0) { //當前頁activeIndex是最後一張,index是第一張,返回length,相差1,表示兩者相鄰且在右側 return length; } else if (index < activeIndex - 1 && activeIndex - index >= length / 2) { // 若是,index在activeIndex前一頁的前面,而且之間的間隔在一半頁數即以上,則返回頁數長度+1,這樣它們會被置於最右側 return length + 1; } else if (index > activeIndex + 1 && index - activeIndex >= length / 2) { // 若是,index在activeIndex後一頁的後面,而且之間的間隔在通常頁數即以上,則返回-2,這樣它們會被置於最左側 return -2; } return index; }, 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; } }, // 這是用來移動幻燈片。 translateItem(index, activeIndex, oldIndex) { // 獲取父組件的寬度 const parentWidth = this.$parent.$el.offsetWidth; // 獲取幻燈片數組的長度 const length = this.$parent.items.length; // 若是不是card模式 if (this.$parent.type !== 'card' && oldIndex !== undefined) { this.animating = index === activeIndex || index === oldIndex; } if (index !== activeIndex && length > 2 && this.$parent.loop) { // 對當前索引進行處理 index = this.processIndex(index, activeIndex, length); } if (this.$parent.type === 'card') { this.inStage = Math.round(Math.abs(index - activeIndex)) <= 1; this.active = index === activeIndex; this.translate = this.calculateTranslate(index, activeIndex, parentWidth); this.scale = this.active ? 1 : CARD_SCALE; } else { this.active = index === activeIndex; // 設置幻燈片的偏移量 this.translate = parentWidth * (index - activeIndex); } this.ready = true; }, handleItemClick() { const parent = this.$parent; if (parent && parent.type === 'card') { const index = parent.items.indexOf(this); parent.setActiveItem(index); } } }, created() { this.$parent && this.$parent.updateItems(); }, destroyed() { this.$parent && this.$parent.updateItems(); } }; </script>