Wasn't the only person to think of a chat scenario for this weeks 408 challenge. Here's my take on it π
Put together using GSAP
.
Enjoy!
h1.status 408 | |
.chat | |
.header.chat__header | |
.header__title New Request | |
.header__members | |
.header__member | |
.header__member.header__member--server | |
.message.message--browser | |
.message__content Hey Server π | |
.message.message--server | |
.message__content Hey Browser π | |
.message.message--browser | |
.message__content Got a quick one for ya π | |
.message.message--server | |
.message__content Sure π | |
.message.message--server | |
.message__content Fire away π« | |
.message.message--browser | |
.message__content Jus' a sec π₯ | |
.message.message--server | |
.message__content OK π | |
.message.message--server | |
.message__content ??? | |
.message.message--server | |
.message__content You still there?? | |
.message.message--server | |
.message__content Ughh π | |
.message.message--left | |
.message__content Server left the chat π | |
.message.message--typing.message--browser | |
.message__content | |
div | |
div | |
div |
const FourOhEight = new TimelineMax({ repeatDelay: 3 }) | |
const typing = document.querySelector('.message--typing') | |
const left = document.querySelector('.message--left') | |
const avatar = document.querySelector('.header__member--server') | |
const messages = document.querySelectorAll( | |
'.message:not(.message--typing):not(.message--left)' | |
) | |
const msgDuration = 0.25 | |
const random = (max, min) => Math.floor(Math.random() * (max - min + 1)) + min | |
const generateChatTL = messages => { | |
const chatTL = new TimelineMax() | |
let stagger = 0 | |
for (let m = 0; m < messages.length; m++) { | |
const message = messages[m] | |
const differentRecipient = | |
message.classList.contains('message--server') && | |
messages[m - 1] && | |
!messages[m - 1].classList.contains('message--server') | |
if ( | |
m !== 0 && | |
m !== messages.length - 2 && | |
(m === 5 || m === 6 || m === 7 || Math.random() > 0.5) | |
) { | |
const deliberation = | |
m === 5 || m === 6 || m === 7 ? Math.random() * 3 : Math.random() | |
chatTL.set(typing, { display: 'flex' }) | |
chatTL.add( | |
TweenMax.to(typing, deliberation, { display: 'none' }), | |
stagger | |
) | |
stagger += deliberation | |
} | |
chatTL.set(message, { scale: 0 }) | |
chatTL.add( | |
TweenMax.to(message, msgDuration, { | |
scale: 1, | |
onStart: () => (message.style.display = 'flex'), | |
}), | |
stagger | |
) | |
let step = differentRecipient ? random(3, 1) : Math.random() | |
if (m === messages.length - 4 || m === messages.length - 2) | |
step = random(4, 2) | |
stagger += m === 0 ? 2 : step | |
} | |
return chatTL | |
} | |
FourOhEight.set([...messages, left], { display: 'none', scale: 0 }) | |
.add(generateChatTL(messages)) | |
.set(left, { scale: 0 }) | |
.add( | |
TweenMax.to(left, msgDuration, { | |
delay: 2, | |
scale: 1, | |
onStart: () => (left.style.display = 'flex'), | |
}) | |
) | |
.add( | |
TweenMax.to(avatar, msgDuration, { | |
scale: 0, | |
}) | |
) | |
.repeat(-1) |
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/TweenMax.min.js"></script> |
@import url('https://fonts.googleapis.com/css?family=Lato') | |
* | |
box-sizing border-box | |
$primary = #e0bbe4 | |
$secondary = #fec8d8 | |
body | |
font-family 'Lato', sans-serif | |
margin 0 | |
padding 0 | |
min-height 100vh | |
display flex | |
align-items center | |
justify-content center | |
flex-direction column | |
background lighten($secondary, 60%) | |
h1 | |
margin 0 | |
.status | |
font-size 100px | |
left 50% | |
top 50% | |
color lighten($secondary, 30%) | |
text-shadow 0 0 5px $secondary | |
margin-bottom 2rem | |
@media(min-width 768px) | |
font-size 200px | |
transform translate(-50%, -50%) rotate(-90deg) translate(0, -140px) translate(0, -50%) | |
position absolute | |
.chat | |
border-radius 15px | |
box-shadow 0 0 20px $primary | |
background #fff | |
width 280px | |
height 250px | |
display flex | |
justify-content flex-end | |
flex-direction column | |
overflow hidden | |
position relative | |
padding 0 15px | |
@media(min-width 768px) | |
width 280px | |
height 400px | |
&__header | |
position absolute | |
top 0 | |
left 0 | |
.header | |
align-items center | |
border-radius 15px 15px 0 0 | |
background darken($primary, 25%) | |
color #fff | |
display flex | |
height 60px | |
padding 20px | |
width 100% | |
text-transform uppercase | |
z-index 2 | |
transform translate(0, 0) | |
&__members | |
display flex | |
align-items center | |
padding-left 15px | |
&__member | |
height 30px | |
width 30px | |
border-radius 100% | |
background linear-gradient(25deg, grey, white) | |
background-image url(https://source.unsplash.com/JelL3CneNDY/50x50) | |
background-size cover | |
margin-right 4px | |
&--server | |
background-image url(https://source.unsplash.com/uWaRsN-CqY0/50x50) | |
.message | |
display none | |
flex 0 0 auto | |
margin-bottom 5px | |
&--browser | |
justify-content flex-start | |
.message__content | |
background #eee | |
&--server | |
justify-content flex-end | |
.message__content | |
background $primary | |
color #fff | |
justify-content flex-end | |
text-align right | |
&__content | |
border-radius 15px | |
min-height 50px | |
align-items center | |
display inline-flex | |
background white | |
padding 10px | |
&--left | |
justify-content center | |
.message__content | |
background #ffffff | |
color #ccc | |
&--typing .message__content | |
div | |
animation wave .5s ease infinite | |
border-radius 100% | |
height 8px | |
width 8px | |
background darken(#eee, 25%) | |
margin 2px | |
for $d in (1..3) | |
&:nth-of-type({$d}) | |
animation-delay $d * .1s | |
@keyframes wave | |
50% | |
transform translate(0, -150%) | |