natasha 1 年之前
父节点
当前提交
b69d070ed8
共有 4 个文件被更改,包括 455 次插入0 次删除
  1. 1 0
      package.json
  2. 8 0
      src/components/carousel/index.js
  3. 162 0
      src/components/carousel/src/item.vue
  4. 284 0
      src/components/carousel/src/main.vue

+ 1 - 0
package.json

@@ -28,6 +28,7 @@
         "normalize.css": "7.0.0",
         "nprogress": "^0.2.0",
         "path-to-regexp": "2.4.0",
+        "swiper": "^5.4.5",
         "view-design": "^4.5.0",
         "vue": "^2.7.14",
         "vue-element-utils": "^0.2.1",

+ 8 - 0
src/components/carousel/index.js

@@ -0,0 +1,8 @@
+import Carousel from './src/main';
+
+/* istanbul ignore next */
+Carousel.install = function(Vue) {
+  Vue.component(Carousel.name, Carousel);
+};
+
+export default Carousel;

+ 162 - 0
src/components/carousel/src/item.vue

@@ -0,0 +1,162 @@
+<template>
+	<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="itemStyle">
+		<div v-if="$parent.type === 'card'" v-show="!active" class="el-carousel__mask">
+		</div>
+		<slot></slot>
+	</div>
+</template>
+
+<script>
+import { autoprefixer } from 'element-ui/src/utils/util';
+// const CARD_SCALE = 0.6;
+// 缩放比例
+const CARD_SCALE = 0.8;
+export default {
+	name: 'ElCarouselItem',
+	props: {
+		name: String,
+		label: {
+			type: [String, Number],
+			default: ''
+		},
+		// 自定义卡片宽度
+		cardwidth: {
+			type: [String, Number],
+			default: '50%'
+		}
+	},
+	data() {
+		return {
+			hover: false,
+			translate: 0,
+			scale: 1,
+			active: false,
+			ready: false,
+			inStage: false,
+			animating: false,
+			parentOffsetWidth: 0 //当前元素父元素宽度
+		};
+	},
+
+	methods: {
+		processIndex(index, activeIndex, length) {
+			if (activeIndex === 0 && index === length - 1) {
+				return -1;
+			} else if (activeIndex === length - 1 && index === 0) {
+				return length;
+			} else if (index < activeIndex - 1 && activeIndex - index >= length / 2) {
+				return length + 1;
+			} else if (index > activeIndex + 1 && index - activeIndex >= length / 2) {
+				return -2;
+			}
+			return index;
+		},
+
+		// 卡片化的carousel重新计算位移量
+		calcCardTranslate(index, activeIndex) {
+			const parentWidth = this.$parent.$el.offsetWidth;
+			this.parentOffsetWidth = parentWidth;
+			if (this.inStage) {
+				let diffWid = (parseInt(this.cardwidth) - this.parentOffsetWidth / 2) / 1.1;
+				let changWid = (parentWidth * ((2 - CARD_SCALE) * (index - activeIndex) + 1)) / 4;
+				//270是基础值,可根据设计图进行修改
+				let moveWid =
+					changWid > 0
+						? changWid === 360
+							? changWid
+							: this.cardwidth == '50%'
+							? changWid 
+							: changWid  + diffWid
+						: this.cardwidth == '50%'
+						? changWid 
+						: changWid - diffWid;
+				return moveWid;
+			} else if (index < activeIndex) {
+				return (-(1 + CARD_SCALE) * parentWidth) / 4;
+			} else {
+				return ((3 + CARD_SCALE) * parentWidth) / 4;
+			}
+		},
+
+		calcTranslate(index, activeIndex, isVertical) {
+			const distance = this.$parent.$el[isVertical ? 'offsetHeight' : 'offsetWidth'];
+			return distance * (index - activeIndex);
+		},
+
+		translateItem(index, activeIndex, oldIndex) {
+			const parentType = this.$parent.type;
+			const parentDirection = this.parentDirection;
+			const length = this.$parent.items.length;
+			if (parentType !== 'card' && oldIndex !== undefined) {
+				this.animating = index === activeIndex || index === oldIndex;
+			}
+			if (index !== activeIndex && length > 2 && this.$parent.loop) {
+				index = this.processIndex(index, activeIndex, length);
+			}
+			if (parentType === 'card') {
+				if (parentDirection === 'vertical') {
+					console.warn('[Element Warn][Carousel]vertical direction is not supported in card mode');
+				}
+				this.inStage = Math.round(Math.abs(index - activeIndex)) <= 1;
+				this.active = index === activeIndex;
+				this.translate = this.calcCardTranslate(index, activeIndex);
+				this.scale = this.active ? 1 : CARD_SCALE;
+			} else {
+				this.active = index === activeIndex;
+				const isVertical = parentDirection === 'vertical';
+				this.translate = this.calcTranslate(index, activeIndex, isVertical);
+			}
+			this.ready = true;
+		},
+
+		handleItemClick() {
+			const parent = this.$parent;
+			if (parent && parent.type === 'card') {
+				const index = parent.items.indexOf(this);
+				parent.setActiveItem(index);
+			}
+		}
+	},
+
+	computed: {
+		parentDirection() {
+			return this.$parent.direction;
+		},
+
+		itemStyle() {
+			const translateType = this.parentDirection === 'vertical' ? 'translateY' : 'translateX';
+			const value = `${translateType}(${this.translate}px) scale(${this.scale})`;
+			// 添加样式,实现自定义卡片宽度
+			const style = {
+				width: this.cardwidth,
+				transform: value,
+				left:
+					this.cardwidth === '50%'
+						? 0
+						: `${-(parseInt(this.cardwidth) - this.parentOffsetWidth / 2) / 2}px`
+			};
+			return autoprefixer(style);
+		}
+	},
+
+	created() {
+		this.$parent && this.$parent.updateItems();
+	},
+
+	destroyed() {
+		this.$parent && this.$parent.updateItems();
+	}
+};
+</script>
+<style scoped>
+.el-carousel__item--card {
+	border-radius: 12px;
+	background: #fff;
+}
+</style>

+ 284 - 0
src/components/carousel/src/main.vue

@@ -0,0 +1,284 @@
+<template>
+	<div :class="carouselClasses" @mouseenter.stop="handleMouseEnter" @mouseleave.stop="handleMouseLeave">
+		<div class="el-carousel__container" :style="{ height: height }">
+			<transition v-if="arrowDisplay" name="carousel-arrow-left">
+				<button type="button" 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 v-if="arrowDisplay" name="carousel-arrow-right">
+				<button type="button" 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>
+		<ul v-if="indicatorPosition !== 'none'" :class="indicatorsClasses">
+			<li v-for="(item, index) in items" :key="index" :class="[
+          'el-carousel__indicator',
+          'el-carousel__indicator--' + direction,
+          { '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>
+import throttle from 'throttle-debounce/throttle';
+import { addResizeListener, removeResizeListener } from 'element-ui/src/utils/resize-event';
+
+export default {
+	name: 'ElCarousel',
+	props: {
+		initialIndex: {
+			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: {
+			type: String,
+			default: 'hover'
+		},
+		type: String,
+		loop: {
+			type: Boolean,
+			default: true
+		},
+		direction: {
+			type: String,
+			default: 'horizontal',
+			validator(val) {
+				return ['horizontal', 'vertical'].indexOf(val) !== -1;
+			}
+		}
+	},
+
+	data() {
+		return {
+			items: [],
+			activeIndex: -1,
+			containerWidth: 0,
+			timer: null,
+			hover: false
+		};
+	},
+
+	computed: {
+		arrowDisplay() {
+			return this.arrow !== 'never' && this.direction !== 'vertical';
+		},
+
+		hasLabel() {
+			return this.items.some((item) => item.label.toString().length > 0);
+		},
+
+		carouselClasses() {
+			const classes = ['my-el-carousel', 'el-carousel--' + this.direction];
+			if (this.type === 'card') {
+				classes.push('el-carousel--card');
+			}
+			return classes;
+		},
+
+		indicatorsClasses() {
+			const classes = ['el-carousel__indicators', 'el-carousel__indicators--' + this.direction];
+			if (this.hasLabel) {
+				classes.push('el-carousel__indicators--labels');
+			}
+			if (this.indicatorPosition === 'outside' || this.type === 'card') {
+				classes.push('el-carousel__indicators--outside');
+			}
+			return classes;
+		}
+	},
+
+	watch: {
+		items(val) {
+			if (val.length > 0) this.setActiveItem(this.initialIndex);
+		},
+
+		activeIndex(val, oldVal) {
+			this.resetItemPosition(oldVal);
+			if (oldVal > -1) {
+				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) {
+			if (this.direction === 'vertical') return;
+			this.items.forEach((item, index) => {
+				if (arrow === this.itemInStage(item, index)) {
+					item.hover = true;
+				}
+			});
+		},
+
+		handleButtonLeave() {
+			if (this.direction === 'vertical') return;
+			this.items.forEach((item) => {
+				item.hover = false;
+			});
+		},
+
+		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() {
+			if (this.timer) {
+				clearInterval(this.timer);
+				this.timer = null;
+			}
+		},
+
+		startTimer() {
+			if (this.interval <= 0 || !this.autoplay || this.timer) return;
+			this.timer = setInterval(this.playSlides, this.interval);
+		},
+
+		setActiveItem(index) {
+			if (typeof index === 'string') {
+				const filteredItems = this.items.filter((item) => item.name === index);
+				if (filteredItems.length > 0) {
+					index = this.items.indexOf(filteredItems[0]);
+				}
+			}
+			index = Number(index);
+			if (isNaN(index) || index !== Math.floor(index)) {
+				console.warn('[Element Warn][Carousel]index must be an integer.');
+				return;
+			}
+			let length = this.items.length;
+			const oldIndex = this.activeIndex;
+			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() {
+		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);
+		this.pauseTimer();
+	}
+};
+</script>