Broken Chat
HTML
<div id="app" class="container">
<div id="wrapper" class="chat-wrapper">
<bubble
v-for="content in contents"
v-bind:bubbtext="content.text"
v-bind:class="content.isUser ? 'bubble-right' : 'bubble-left'"
>
</bubble>
<div v-show="isTyping" class="bubble bubble-right bubble-thinking">
<span class="dot"></span>
<span class="dot"></span>
<span class="dot"></span>
</div>
<p v-if="noResponses" class="small disabled">User has left the chat. ☹️</p>
</div>
<div class="chat-input">
<input
v-on:keyup.enter="addToChat"
v-model="newMessage"
type="text"
name="chatText"
id="chatText"
class="input-control input-text"
placeholder="Type a message"
/>
<button v-on:click="addToChat" class="input-control input-button">Send</button>
</div>
</div>
SCSS
@import 'https://fonts.googleapis.com/css?family=Roboto+Condensed:300';
$primary-color: #d573ee;
$primary-color-dark: darken( $primary-color, 10% );
$primary-color-tint: lighten( $primary-color, 20% );
$primary-font: "Roboto Condensed", sans-serif;
$black: #444;
$white: #fff;
* { box-sizing: border-box; }
body {
display: flex;
justify-content: center;
background-color: $primary-color;
font-family: $primary-font;
font-size: 16px;
line-height: 1.875em;
}
.container {
flex: 1 0 auto;
margin-top: 50px;
margin-bottom: 50px;
// padding: 30px;
max-width: 500px;
box-shadow: 10px 10px 0 $primary-color-dark;
background-color: $white;
}
.small {
font-size: 0.75em;
line-height: 1.5em;
}
.disabled {
color: lighten($black, 50%);
}
.chat-wrapper {
display: flex;
flex-direction: column;
// justify-content: space-between;
// align-items: flex-end;
padding: 30px;
height: 400px;
overflow-y: auto;
}
.bubble {
position: relative;
width: auto;
max-width: 350px;
margin-bottom: 20px;
padding: 20px 40px;
border-radius: 25px;
// box-shadow: 0 1px 8px 0 rgba(0,0,0,0.1),0 3px 4px 0 rgba(0,0,0,0.12),0 3px 3px -2px rgba(0,0,0,0.10);
box-shadow: 5px 5px 0 $primary-color-dark;
font-weight: 300;
transform: scale(0);
animation-name: scaleAll;
animation-duration: 0.35s;
animation-timing-function: cubic-bezier(0,0.51,0.31,1.51);
animation-fill-mode: forwards;
&-right {
margin-left: auto;
border-bottom-right-radius: 3px;
background-color: $primary-color-tint;
transform-origin: bottom right;
}
&-left {
margin-right: auto;
border-bottom-left-radius: 3px;
background-color: $primary-color;
color: $white;
transform-origin: bottom left;
+ .bubble-left {
animation-delay: 1s;
+ .bubble-left {
animation-delay: 2s;
}
}
}
.dot {
margin: 0 2px;
height: 10px;
width: 10px;
border-radius: 50%;
background-color: rgba($primary-color-dark, 0.7);
line-height: 1em;
display: inline-block;
animation-name: bouncyBlink;
animation-duration: 0.9s;
animation-iteration-count: infinite;
animation-timing-function: ease-in-out;
&:nth-child(2) {
animation-delay: 0.1s;
-webkit-animation-delay: 0.1s;
}
&:nth-child(3) {
animation-delay: 0.2s;
-webkit-animation-delay: 0.2s;
}
}
}
.input-control {
padding: 15px 20px;
font-family: $primary-font;
border-radius: 3px;
border: none;
font-size: 1em;
}
.input-button {
background-color: $primary-color-dark;
color: $white;
transition: 0.35s;
cursor: pointer;
&:hover {
background-color: $primary-color;
}
}
.chat-input {
display: flex;
flex-wrap: wrap;
padding: 20px;
background-color: $primary-color-tint;
.input-text {
flex: 1 80%;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.input-button {
flex: 1 auto;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
}
@keyframes scaleAll {
from { transform: scale(0) }
to { transform: scale(1) }
}
@keyframes bouncyBlink {
0%, 45%, 100% { background-color: rgba($primary-color-dark, 0.5); transform: translateY(0) }
25% { background-color: rgba($primary-color-dark, 1); transform: translateY(-10px) }
35% { background-color: rgba($primary-color-dark, 0.75); transform: translateY(5px) }
}
JAVASCRIPT
const data = [
{ text: 'Hello Stranger!' },
{ text: 'How\'s it going?' },
{ text: 'Great.' },
{ text: 'Awesome.' },
{ text: 'Bravo!' },
{ text: 'See ya later!' }
];
let counter = 1;
Vue.component('bubble', {
props: ['bubbtext'],
template: `<div class="bubble">{{bubbtext}}</div>`
})
var vm = new Vue({
el: '#app',
data: {
noResponses: false,
isTyping: false,
newMessage: '',
contents: []
},
watch: {
newMessage: function() {
if (this.newMessage !== '') {
this.isTyping = true;
setTimeout(moveChat, 100);
} else {
this.isTyping = false;
}
}
},
methods: {
addToChat: function() {
if (this.newMessage !== '') {
this.contents.push({text: this.newMessage, isUser: true});
this.newMessage = "";
counter++;
setTimeout(moveChat, 100);
setTimeout(this.addNewResponse, 1000);
}
},
addNewResponse: function() {
if (counter < data.length) {
this.contents.push(data[counter]);
} else {
this.noResponses = true;
}
setTimeout(moveChat, 100);
}
}
})
vm.contents.push(data[0]);
vm.contents.push(data[1]);
function moveChat() {
const wrap = document.getElementById('wrapper');
wrap.scrollTop = wrap.scrollHeight;
}