修改elementUI carousel组件源码实现轮播图卡片化宽度自适应及样式调整

项目中要实现下图效果三
在这里插入图片描述
说明:效果二和效果三是说明不同宽度达到的组件的效果

纯css实现宽度自定义并居中

通过elementUI API中发现没有暴露出轮播图卡片化的自定义的宽度,通过查看样式得知是固定的50%的宽度,那么要实现修改宽度可以进行样式覆盖,代码如下:

.el-carousel__container {
	.el-carousel__item--card {
		width: 850px !important;
		background: #fff;
	}
	.el-carousel__item {
		width: 850px !important;
		background-color: #d3dce6;
	}
	.el-carousel__item--card.is-active {
		z-index: 2;
		position: absolute;
		left: 50%;
		transform: translate(-50%, 0px) !important;
	}
}

但是这样看上去切换动画效果有些奇怪,且这样并不能满足项目对效果三的需求,所以对elementUI的carousel组件进行修改。
具体操作如下
首先找到:node_modules —— element-ui —— packages —— carousel,把整个文件夹复制出来,单独在项目中另行引入组件,不影响其他模块再次使用这个carousel组件

实现效果三的完整代码:

说明:main.vue动的不多(也可能没动,忘记了),主要修改的是item.vue文件,且没有删除使用非卡片功能的代码,可以直接使用这个组件实现异形非卡片功能的轮播图。
页面引入:

import carousel from './components/carousel/src/main';
import carouselItem from './components/carousel/src/item';
export default {
	name: 'Login',
	components: {
		carousel,
		carouselItem
	},
};

页面直接使用:(暴露出自定义宽度字段:cardwidth)

<carousel class="service-advantage" :interval="40000000" type="card" height="200px">
	<carousel-item v-for="(item,index) in 5" :key="`a3${index}`" cardwidth="650px">
		<h3 class="medium">{{ item }}</h3>
	</carousel-item>
</carousel>

具体对组件进行修改的完整代码:
main.vue

<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>

item.vue

<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 + 270
							: changWid + 270 + diffWid
						: this.cardwidth == '50%'
						? changWid - 270
						: changWid - 270 - 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>
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值