Vue Touch HammerJs Example and Demo

Vue Touch HammerJs Example and Demo are developed using CSS, HTML and HammerJs.

Demo Download

Author Lisi
Hits
Created AUGUST 10, 2018
License Open
Compatible browsers Chrome, Firefox, Safari

HTML Snippet

<main class="" id="app" v-pan="onPan">
	<div class="emoji" ref="emoji">{{ selectedContent }}</div>
	<section class="slider">
		<ul class="slider__list" ref="list">
			<li v-for="(animal, index) in animals" :key="animal"
					class="slider__item" 
					v-tap="(e) => onTap(e, animal)"
					:style="{backgroundColor: colors[index]}">
				{{ animal }}
			</li>
		</ul>
	</section>
</main>

SCSS Code

.slider {
	width: 100%;
	height: 120px;
	overflow: visible;
  position: relative;
  white-space: nowrap;

	&__list {
		display: flex;
		width: 100%;
		height: 100%;
		
		font-size: 2rem;
		backface-visibility: hidden;
		transform: translateX(calc(var(--x, 0) * 1%));
	}
	
	&__item {
		position: relative;
		flex: 0 0 140px;
		
		display: flex;
		justify-content: center;
		align-items: center;
		height: 100%;
		margin-right: 12px;
		padding: 6px;
		box-sizing: border-box;
		
		border-radius: 8px;
		text-align: center;
  	transition: opacity 0.15s ease;
		color: #fff;

		&:focus {
			opacity: 0.8;
		}
	}
}

.emoji  {
	padding: 40px;
	font-size: 6rem;
	min-height: 6rem;
	backface-visibility: hidden;
}


/* layout */
html {
	height: 100%;
	display: flex;
	background: #155e63;
}

body {	
	position: relative;

	width: 100%;
	height: 100%;
	max-width:  360px;
	max-height: 640px;
	margin: auto;

	background: #efefef;
	font-family:'Do Hyeon', sans-serif;
	font-size: 16px;
}

#app {
	height: 100%;
	width: 100%;
	display: flex;
	flex-direction: column;
	justify-content: center;
	align-items: center;
	padding: 20px;
	box-sizing: border-box;
	
	overflow: hidden;
}

JavaScript Snippet

Vue.directive("pan", {
	bind: function(el, binding) {
		if (typeof binding.value === "function") {
			const mc = new Hammer(el);
			mc.get("pan").set({ direction: Hammer.DIRECTION_ALL });
			mc.on("pan", binding.value);
		}
	}
});

Vue.directive("tap", {
	bind: function(el, binding) {
		if (typeof binding.value === "function") {
			const mc = new Hammer(el);
			mc.on("tap", binding.value);
		}
	}
});

const app = new Vue({
	el: "#app",
	data: {
		animals: [
			"cat",
			"dog",
			"panda",
			"lion",
			"frog",
			"bear",
			"mouse",
			"tiger",
			"monkey"
		],
		emojis: ["ðߐ±", "ðߐ¶", "ðߐ¼", "ð�¦", "🐸", "🐻", "🐹", "🐯", "🐵"],
		colors: [
			"#F7CC45",
			"#AC6909",
			"#272625",
			"#FFAD01",
			"#81DC58",
			"#C68E71",
			"#F2B2BD",
			"#FFCB00",
			"#BE9763"
		],
		currentOffset: 0,
		selected: "cat"
	},
	computed: {
		overflowRatio() {
			return this.$refs.list.scrollWidth / this.$refs.list.offsetWidth;
		},
		itemWidth() {
			return this.$refs.list.scrollWidth / this.animals.length;
		},
		selectedContent() {
			if (this.selected) {
				return this.emojis[this.animals.indexOf(this.selected)];
			}
			return "";
		},
		count() {
			return this.animals.length
		}
	},
	watch: {
		selected(newValue) {
			TweenMax.fromTo(
				this.$refs.emoji,
				0.6,
				{ scale: 0 },
				{ scale: 1, ease: Elastic.easeOut.config(1, 0.8) }
			);
		}
	},
	methods: {
		onPan(e) {
			const dragOffset = 100 / this.itemWidth * e.deltaX / this.count * this.overflowRatio;

			const transform = this.currentOffset + dragOffset;

			this.$refs.list.style.setProperty("--x", transform);

			if (e.isFinal) {
				this.currentOffset = transform;
				const maxScroll = 100 - this.overflowRatio * 100;
				let finalOffset = this.currentOffset;

				// scrolled to last item
				if (this.currentOffset <= maxScroll) {
					finalOffset = maxScroll;
				} else if (this.currentOffset >= 0) {
					// scroll to first item
					finalOffset = 0;
				} else {
					// animate to next item according to pan direction
					const index = this.currentOffset / this.overflowRatio / 100 * this.count;
					const nextIndex = e.deltaX <= 0 ? Math.floor(index) : Math.ceil(index);
					finalOffset = 100 * this.overflowRatio / this.count * nextIndex;
				}

				// bounce back animation
				TweenMax.fromTo(
					this.$refs.list,
					0.4,
					{ '--x': this.currentOffset },
					{
						'--x': finalOffset,
						ease: Elastic.easeOut.config(1, 0.8),
						onComplete: () => {
							this.currentOffset = finalOffset;
						}
					}
				);
			}
		},
		onTap(e, value) {
			if (value) {
				TweenMax.to(e.target, 0.12, { scale: 1.1, yoyo: true, repeat: 1, ease: Sine.easeOut})
				this.selected = value;
			}
		}
	}
});

Preview

Vue Touch HammerJs Example and Demo preview

Advertisement

Google Matched Content...