<body>
	<div id="app">
		<!-- Exploding button: contact -->
		
		<section>
			<explode-button
				open-text='Contact'
				close-text='Dismiss'
				:children="[
					{
						text: 'DEV',
						href: 'https://dev.to/adam_cyclones'
					},
					{
						text: 'Email',
					},
					{
						text: 'Phone',
					},
					{
						text: 'Twitter',
					},
					{
						text: 'Fax',
					}
			]"/>
		</section>
		
		<!-- /End Exploding button: contact -->
	</div>
</body>

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
	background: floralwhite;
}

.exp {
	
	position: relative;
	display: flex;
	justify-content: center;
	align-items: center;
	
	&-Button,
	&-Children_Button {
		user-select: none;
		position: absolute;
		padding: .3rem .8rem;
		border-radius: 9e9em;
		background: #fff;
		color: #3c4437;
		border: 1px solid #3c4437;
		font-size: 1.2rem;
		text-transform: capitalize;
		cursor: pointer;
		box-sizing: border-box;
		
		&:focus {
			outline: 0;
			box-shadow: 0 0 0 3px rgba(0, 123, 255, .5);
		}
	}
	
	&-Button {
		z-index: 2;
		background-color: #5d5761;
		color: #f9f9f9;
		border: 0;
	}

	.exp-Children {
		z-index: 1;
	}
	
	&-Children_Button {
		will-change: transform;
	}
	
	&-Children {
		position: absolute;
		padding: 0;
		margin: 0;
		top: 0;
		left: 0;
		height: inherit;
    width: inherit;
		display: flex;
		align-items: center;
		justify-content: center;
	}
	
	&-Menu_List {
		position: absolute;
		padding: 0;
		margin: 0;
		top: 0;
		left: 0;
	}
}


View Compiled

const explodeButton = Vue.component('explode-button', {
	template: `
		<div class='exp' :style='{
			width: CSS.px(radius * 2).toString(), height: 			CSS.px(radius * 2),
			}'>
			<button 
				class='exp-Button'
				@mousedown='explodeImplode'
				@mouseover="isMoused = true"
				@mouseleave="isMoused = false"
				type='button'>{{text}}</button>
			<div class='exp-Children'>
				<button 
					v-if='child.text && !child.href'
					@click='setCurrent'
					@mouseover="isMoused = true"
					@mouseleave="isMoused = false"
					class='exp-Children_Button'
					v-for='(child, index) in children'
					v-show='!getInitialIteraction'
					:style='"transform:" + setPosition(index) + "; will-change: transform;"'>
					{{child.text}}
				</button>
				<a
					target='_blank'
					:href='child.href'
					v-if='child.text && child.href'
					@click='setCurrent'
					@mouseover="isMoused = true"
					@mouseleave="isMoused = false"
					class='exp-Children_Button'
					v-for='(child, index) in children'
					v-show='!getInitialIteraction'
					:style='"transform:" + setPosition(index) + "; will-change: transform;"'>
					{{child.text}}
					<i v-if='child.icon' :class="'fab ' + child.icon"></i>
				</a>
			</div>
		</div>
	`,
	props: {
		children: {
			type: Array,
			required: true
		},
		openText: {
			type: String,
			required: true
		},
		closeText: {
			type: String,
			required: true
		},
		shape: {
			type: String,
			default: 'circle'
		}
	},
	data () {
		return {
			isMoused: false,
			isTouched: false,
			animation: {
				explode: {
					timing: ({delay}) => ({
						duration: 200,
						direction: 'reverse',
						fill: 'forwards',
						easing: 'ease-in',
						delay
					})
				},
				implode: {
					timing: ({interactions}) => ({
						duration: 300,
						direction: 'reverse',
						fill: 'forwards',
						easing: 'ease-out'
					})	
				}
			},
			spread: 360,
			stagger: true,
			open: false,
			hideChildren: true,
			intereactions: 0,
			radius: 100
		}
	},
	computed: {
		getSliceAngle () {
			return this.spread / this.$props.children.length;
		},
		getLen () {
			return this.$props.children.length;
		},
		getInitialIteraction () {
			return this.intereactions === 0;
		},
		text () {
			return this.open ? this.closeText : this.openText;
		},
	},
	methods: {
		constrainAngle(angle) {
			angle = Math.abs(angle);
			let ret = angle <= this.spread ? angle : 0;
			while (angle > this.spread) {
				angle -= this.spread;
				ret = angle;
			}
			return ret;
		},
		doAnimation (animationName) {
			const wasExplode = animationName === 'explode';
			const children = this.$el.querySelectorAll('.exp-Children_Button');
			
			const explode = (child) => [
				{ // to
					visibility: 'visible',
					transform: `rotate(0) ${child.style.transform}`
				},
				{ // from
					visibility: 'hidden',
					transform: 'rotate(0) translate3d(0, 0, 0)'
				},
			];
			const implode = (child) => [
				{
					transform: 'rotate(0) translate3d(0, 0, 0)',
					visibility: 'hidden',
				},
				{
					transform: `rotate(0) ${child.style.transform}`,
					visibility: 'visible',
				},
			];
			return new Promise(resolve => {
				for (let multiplier = 0; multiplier < this.getLen; multiplier ++) {
					const child = children[multiplier];

					if ( this.intereactions === 1 ) {
						child.style.visibility = 'hidden';
					} else {
						child.style.visibility = 'visible';
					}

					const animation = child.animate(
						wasExplode ? explode(child) : implode(child),
						this.animation[animationName].timing({
							delay: this.stagger ? multiplier * 100 : 0
						})
					);
				}
				this.open = wasExplode;
			});
		},
		explodeImplode () {
			if (!this.isTouched) {
				this.incrIntereactions();
				if (this.open) {
					this.doAnimation('implode');
				} else {
					this.doAnimation('explode');
				}
			}
		},
		calculateCoordinates (
			angle
		) {
			const x = this.radius * Math.sin(Math.PI * 2 * angle / this.spread);
			const y = this.radius * Math.cos(Math.PI * 2 * angle / this.spread);
			const coordinates = {x, y};
			return coordinates;
		},
		setPosition (multiplier) {
			// where multiplier is index
			const angle = this.constrainAngle(this.getSliceAngle * multiplier);
			
			const shape = {
				circle: 1,
				semi: 2,
				quater: 4
			}
			
			const north = 0;
			
			const { x, y } = this.calculateCoordinates( this.constrainAngle( (angle / shape[this.shape]) - north ));
			const cssUnitX = CSS.px(x).toString();
			const cssUnitY = CSS.px(y).toString();
			const translate = `translate3d(${cssUnitX}, ${cssUnitY}, 0)`;
			return translate;
		},
		incrIntereactions () {
			this.intereactions += 1;
		},
		setCurrent ({ target }) {
			if (!this.isMoused) {
				target.setAttribute('aria-current', true);
			}
		},
		unsetCurrent ({ target }) {
			if (!this.isMoused) {
				target.setAttribute('aria-current', false);
			}
		}
	}
});

new Vue({
	el: '#app',
	components: {
		explodeButton
	}
});

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js