Your shirt order blasts off from a t-shirt cannon in this playful button concept from Jhey, built with GSAP.
See the Pen T-Shirt Cannon Button 🚀 by Jhey (@jh3y) on CodePen.
- const POSITIONS = ['middle', 'left', 'right', 'bottom']
mixin shirt(posIndex)
svg.t-shirt(class=`t-shirt--${POSITIONS[posIndex]}` xmlns='http://www.w3.org/2000/svg' width='245' height='230' viewbox='0 0 64.8 60.9')
defs
if (posIndex === 0)
clipPath#clipMain
rect(width="65" height="61")
if (posIndex === 1)
clipPath#clipLeft
rect(width="22.5" height="61")
if (posIndex === 2)
clipPath#clipRight
rect(x="42.3" width="22.5" height="61")
g.t-shirt__shirt(stroke='#000')
if (posIndex === 1 || posIndex === 2)
g.t-shirt__arm(class=`t-shirt__arm--${posIndex === 1 ? 'left' : 'right'}` clip-path=`url(#clip${posIndex === 1 ? 'Left' : 'Right'})`)
path(d='M251.8 109.2a36 17.5 0 01-34 11.6 36 17.5 0 01-33.9-11.6l-31.5 4.8-50 50 37 36.8 13-13v142.7h130.9V187.7l13.1 13.1 36.9-36.8-50-50z' transform='matrix(.26468 0 0 .2626 -25.2 -27.2)' stroke-width='5' stroke-linecap='square')
if (posIndex === 0)
g.t-shirt__middle(clip-path="url(#clipMain)")
path(d='M90.5 151.3a9.5 4.6 0 01-9 3 9.5 4.6 0 01-9-3l-2.3.4v58.2h22.7v-58.2z' stroke-width='1.3' stroke-linecap='square' transform='matrix(1.00036 0 0 .99247 -49.2 -148.7)')
if (posIndex === 3)
g.t-shirt__fold
path(stroke-width='1.3' stroke-linecap='round' stroke-linejoin='round' d='M70.2 197.8h22.7v12H70.2z' transform='matrix(1.00036 0 0 .99247 -49.2 -148.7)')
mixin cannon(posIndex)
svg(class=`${posIndex === 0 ? 'cannon__shirt' : 'cannon'}` xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' viewbox='0 0 16.7 87.1')
if (posIndex === 0)
g
path(stroke='#000' stroke-width='1.3' stroke-linecap='round' stroke-linejoin='round' d='M55.1 223.9h22.7v12H55.1z' transform='matrix(0 -1.00036 .99247 0 -219.8 98)')
if (posIndex === 1)
g(transform='matrix(0 -1.00036 .99247 0 -219.8 98)')
path.cannon__plastic(stroke='#000' stroke-width='1.3' stroke-linecap='round' stroke-linejoin='round' d='M11.6 222.1h85.7v15.5H11.6z')
rect.cannon__shine(width='20.4' height='1.9' x='63.2' y='223.7' ry='1')
g(stroke='#000' stroke-linecap='round' stroke-linejoin='round')
path.cannon__band(transform='matrix(-.26547 0 0 -.24756 81.3 272.7)' d='M-59.7 143v60.6h25.3v-60.7z' stroke-width='6.3')
button
.button
.t-shirt__cannon.button__cannon
.t-shirt__cannon-content
+cannon(0)
+cannon(1)
.t-shirt__container
.t-shirt__wrapper.button__shirt
//- Middle
+shirt(0)
//- Left arm
+shirt(1)
//- Right arm
+shirt(2)
//- Bottom
+shirt(3)
.button__text
.dummy Ordered
.text.text--order(data-splitting='') Order
.text.text--ordered(data-splitting='') Ordered
<button>
<div class="button">
<div class="t-shirt__cannon button__cannon">
<div class="t-shirt__cannon-content">
<svg class="cannon__shirt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewbox="0 0 16.7 87.1">
<g>
<path stroke="#000" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" d="M55.1 223.9h22.7v12H55.1z" transform="matrix(0 -1.00036 .99247 0 -219.8 98)"></path>
</g>
</svg>
<svg class="cannon" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewbox="0 0 16.7 87.1">
<g transform="matrix(0 -1.00036 .99247 0 -219.8 98)">
<path class="cannon__plastic" stroke="#000" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" d="M11.6 222.1h85.7v15.5H11.6z"></path>
<rect class="cannon__shine" width="20.4" height="1.9" x="63.2" y="223.7" ry="1"></rect>
<g stroke="#000" stroke-linecap="round" stroke-linejoin="round">
<path class="cannon__band" transform="matrix(-.26547 0 0 -.24756 81.3 272.7)" d="M-59.7 143v60.6h25.3v-60.7z" stroke-width="6.3"></path>
</g>
</g>
</svg>
</div>
</div>
<div class="t-shirt__container">
<div class="t-shirt__wrapper button__shirt">
<svg class="t-shirt t-shirt--middle" xmlns="http://www.w3.org/2000/svg" width="245" height="230" viewbox="0 0 64.8 60.9">
<defs>
<clipPath id="clipMain">
<rect width="65" height="61"></rect>
</clipPath>
</defs>
<g class="t-shirt__shirt" stroke="#000">
<g class="t-shirt__middle" clip-path="url(#clipMain)">
<path d="M90.5 151.3a9.5 4.6 0 01-9 3 9.5 4.6 0 01-9-3l-2.3.4v58.2h22.7v-58.2z" stroke-width="1.3" stroke-linecap="square" transform="matrix(1.00036 0 0 .99247 -49.2 -148.7)"></path>
</g>
</g>
</svg>
<svg class="t-shirt t-shirt--left" xmlns="http://www.w3.org/2000/svg" width="245" height="230" viewbox="0 0 64.8 60.9">
<defs>
<clipPath id="clipLeft">
<rect width="22.5" height="61"></rect>
</clipPath>
</defs>
<g class="t-shirt__shirt" stroke="#000">
<g class="t-shirt__arm t-shirt__arm--left" clip-path="url(#clipLeft)">
<path d="M251.8 109.2a36 17.5 0 01-34 11.6 36 17.5 0 01-33.9-11.6l-31.5 4.8-50 50 37 36.8 13-13v142.7h130.9V187.7l13.1 13.1 36.9-36.8-50-50z" transform="matrix(.26468 0 0 .2626 -25.2 -27.2)" stroke-width="5" stroke-linecap="square"></path>
</g>
</g>
</svg>
<svg class="t-shirt t-shirt--right" xmlns="http://www.w3.org/2000/svg" width="245" height="230" viewbox="0 0 64.8 60.9">
<defs>
<clipPath id="clipRight">
<rect x="42.3" width="22.5" height="61"></rect>
</clipPath>
</defs>
<g class="t-shirt__shirt" stroke="#000">
<g class="t-shirt__arm t-shirt__arm--right" clip-path="url(#clipRight)">
<path d="M251.8 109.2a36 17.5 0 01-34 11.6 36 17.5 0 01-33.9-11.6l-31.5 4.8-50 50 37 36.8 13-13v142.7h130.9V187.7l13.1 13.1 36.9-36.8-50-50z" transform="matrix(.26468 0 0 .2626 -25.2 -27.2)" stroke-width="5" stroke-linecap="square"></path>
</g>
</g>
</svg>
<svg class="t-shirt t-shirt--bottom" xmlns="http://www.w3.org/2000/svg" width="245" height="230" viewbox="0 0 64.8 60.9">
<defs>
</defs>
<g class="t-shirt__shirt" stroke="#000">
<g class="t-shirt__fold">
<path stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" d="M70.2 197.8h22.7v12H70.2z" transform="matrix(1.00036 0 0 .99247 -49.2 -148.7)"></path>
</g>
</g>
</svg>
</div>
</div>
<div class="button__text">
<div class="dummy">Ordered</div>
<div class="text text--order" data-splitting="">Order</div>
<div class="text text--ordered" data-splitting="">Ordered</div>
</div>
</div>
</button>
script.js
const {
Splitting,
gsap: { timeline, set },
} = window
const CLIP = new Audio(
'https://s3-us-west-2.amazonaws.com/s.cdpn.io/605876/t-shirt-cannon-pop.mp3'
)
// Split the order letter
Splitting()
const SHIRT_SEGMENTS = [...document.querySelectorAll('.t-shirt')]
const SHIRT = document.querySelector('.t-shirt__wrapper')
const LEFT_ARM = SHIRT_SEGMENTS[1]
const RIGHT_ARM = SHIRT_SEGMENTS[2]
const FOLD = SHIRT_SEGMENTS[3].querySelector('.t-shirt__fold')
const CLIPS = [...document.querySelectorAll('clipPath rect')]
const BUTTON = document.querySelector('button')
document.documentElement.style.setProperty('--hue', Math.random() * 360)
set(FOLD, { transformOrigin: '50% 100%', scaleY: 0 })
set(CLIPS, { transformOrigin: '50% 0' })
set('.cannon__shirt', { opacity: 0 })
set('.cannon', { y: 28 })
set('.text--ordered .char', { y: '100%' })
const SPEED = 0.15
const FOLD_TL = () =>
new timeline()
.to(
LEFT_ARM,
{
duration: SPEED,
rotateY: -180,
transformOrigin: `${(22 / 65.3) * 100}% 50%`,
},
0
)
.to(
RIGHT_ARM,
{
duration: SPEED,
rotateY: -180,
transformOrigin: `${((65.3 - 22) / 65.3) * 100}% 50%`,
},
SPEED
)
.to(FOLD, { duration: SPEED / 4, scaleY: 1 }, SPEED * 2)
.to(FOLD, { duration: SPEED, y: -47 }, SPEED * 2 + 0.01)
.to(CLIPS, { duration: SPEED, scaleY: 0.2 }, SPEED * 2)
.to('.cannon', { duration: SPEED, y: 0 }, SPEED * 2)
// FOLD_TL()
const LOAD_TL = () =>
new timeline()
.to('.button__shirt', {
transformOrigin: '50% 13%',
rotate: 90,
duration: 0.15,
})
.to('.button__shirt', {
duration: 0.15,
y: 60,
})
.to('.t-shirt__cannon', {
y: 5,
repeat: 1,
yoyo: true,
duration: 0.1,
})
.to('.t-shirt__cannon', {
y: 50,
duration: 0.5,
delay: 0.1,
})
const FIRE_TL = () =>
new timeline()
.set('.t-shirt__cannon', {
rotate: 48,
x: -85,
scale: 2.5,
})
.set('.cannon__shirt', { opacity: 1 })
.to('.t-shirt__cannon-content', { duration: 1, y: -35 })
.to('.t-shirt__cannon-content', { duration: 0.25, y: -37.5 })
.to('.t-shirt__cannon-content', { duration: 0.015, y: -30.5 })
.to(
'.cannon__shirt',
{ onStart: () => CLIP.play(), duration: 0.5, y: '-25vmax' },
'<'
)
.to('.text--ordered .char', { duration: 0.15, stagger: 0.1, y: '0%' })
.to('button', { duration: 7 * 0.15, '--hue': 116, '--lightness': 55 }, '<')
const ORDER_TL = new timeline({ paused: true })
ORDER_TL.set('.cannon__shirt', { opacity: 0 })
ORDER_TL.set('button', { '--hue': 260, '--lightness': 20 })
ORDER_TL.to('button', { scale: 300 / BUTTON.offsetWidth, duration: SPEED })
ORDER_TL.to('.text--order .char', { stagger: 0.1, y: '100%', duration: 0.1 })
ORDER_TL.to(SHIRT, {
// Based on styling. 25px + 0.5rem
x: BUTTON.offsetWidth / 2 - 33,
duration: 0.2,
})
// ORDER_TL.to(BUTTON, { scale: 3 })
ORDER_TL.add(FOLD_TL())
ORDER_TL.add(LOAD_TL())
ORDER_TL.add(FIRE_TL())
BUTTON.addEventListener('click', () => {
if (ORDER_TL.progress() === 1) {
// ORDER_TL.restart()
document.documentElement.style.setProperty('--hue', Math.random() * 360)
ORDER_TL.time(0)
ORDER_TL.pause()
} else if (ORDER_TL.progress() === 0) {
ORDER_TL.play()
}
})
styles
<script src="https://unpkg.com/splitting/dist/splitting.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.2.6/gsap.min.js"></script>
*
box-sizing border-box
body
min-height 100vh
display flex
align-items center
justify-content center
overflow hidden
:root
--color 'hsl(%s, 80%, 60%)' % var(--hue)
.t-shirt
height 100%
width 100%
position absolute
top 0
left 0
&__shirt
fill var(--color)
&__wrapper
position relative
&__cannon
position absolute
left 50%
top 50%
width 10px
transform translate(-50%, 0)
svg
position absolute
top 0
left 0
.cannon__shirt path
fill var(--color)
.cannon__band
fill hsl(50, 100%, 50%)
.cannon__plastic
fill hsla(190, 80%, 80%, 0.35)
.cannon__shine
fill hsla(0, 0%, 100%, 0.5)
.button
font-family sans-serif
font-weight bold
font-size 1rem
padding 1rem 2rem
padding-left calc(1rem + 50px)
position relative
border-radius 6px
border 0
color hsl(0, 0%, 100%)
outline transparent
min-width 120px
$clip = inset(-1000% -1000% 0 0)
// $clip = inset(-1000% -1000% -1000% -1000%)
-webkit-clip-path $clip
clip-path $clip
&__text
position relative
.dummy
color transparent
& > .text
position absolute
top 0
left 0
white-space nowrap
.word
display inline-block
-webkit-clip-path inset(0 0 0 0)
clip-path inset(0 0 0 0)
.char
display inline-block
&__shirt
position absolute
height 32px
width 32px
top 50%
left calc(0.5rem + 25px)
transform translate(-50%, -50%)
// Don't know why but I needed this little wrapper piece to hide the t-shirt
.t-shirt__container
position absolute
top 0
right 0
bottom 0
left 0
overflow hidden
border-radius 6px
button
--hue 260
cursor pointer
background transparent
padding 0
border 0
border-radius 6px
outline transparent
background 'hsl(%s, 46%, %s)' % (var(--hue, 260) calc(var(--lightness, 20) * 1%))
box-shadow 2px 2px 4px 0px #333
transition box-shadow .15s
&:active
box-shadow 0px 0px 0px 0px #333
* {
box-sizing: border-box;
}
body {
min-height: 100vh;
display: -webkit-box;
display: flex;
-webkit-box-align: center;
align-items: center;
-webkit-box-pack: center;
justify-content: center;
overflow: hidden;
}
:root {
--color: hsl(var(--hue), 80%, 60%);
}
.t-shirt {
height: 100%;
width: 100%;
position: absolute;
top: 0;
left: 0;
}
.t-shirt__shirt {
fill: var(--color);
}
.t-shirt__wrapper {
position: relative;
}
.t-shirt__cannon {
position: absolute;
left: 50%;
top: 50%;
width: 10px;
-webkit-transform: translate(-50%, 0);
transform: translate(-50%, 0);
}
.t-shirt__cannon svg {
position: absolute;
top: 0;
left: 0;
}
.cannon__shirt path {
fill: var(--color);
}
.cannon__band {
fill: #ffd500;
}
.cannon__plastic {
fill: rgba(163,231,245,0.35);
}
.cannon__shine {
fill: rgba(255,255,255,0.5);
}
.button {
font-family: sans-serif;
font-weight: bold;
font-size: 1rem;
padding: 1rem 2rem;
padding-left: calc(1rem + 50px);
position: relative;
border-radius: 6px;
border: 0;
color: #fff;
outline: transparent;
min-width: 120px;
-webkit-clip-path: inset(-1000% -1000% 0 0);
clip-path: inset(-1000% -1000% 0 0);
}
.button__text {
position: relative;
}
.button__text .dummy {
color: transparent;
}
.button__text > .text {
position: absolute;
top: 0;
left: 0;
white-space: nowrap;
}
.button .word {
display: inline-block;
-webkit-clip-path: inset(0 0 0 0);
clip-path: inset(0 0 0 0);
}
.button .char {
display: inline-block;
}
.button__shirt {
position: absolute;
height: 32px;
width: 32px;
top: 50%;
left: calc(0.5rem + 25px);
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}
.t-shirt__container {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow: hidden;
border-radius: 6px;
}
button {
--hue: 260;
cursor: pointer;
background: transparent;
padding: 0;
border: 0;
border-radius: 6px;
outline: transparent;
background: hsl(var(--hue, 260), 46%, calc(var(--lightness, 20) * 1%));
box-shadow: 2px 2px 4px 0px #333;
-webkit-transition: box-shadow 0.15s;
transition: box-shadow 0.15s;
}
button:active {
box-shadow: 0px 0px 0px 0px #333;
}
Leave a Reply