Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save PedroPastel/fc06ad4b26b69df3994184ac93f3dedd to your computer and use it in GitHub Desktop.
Save PedroPastel/fc06ad4b26b69df3994184ac93f3dedd to your computer and use it in GitHub Desktop.
Animal Crossing: New Horizons' Wobbly Dialogue Bubble Demo (HTML, CSS, & JS)

Animal Crossing: New Horizons' Wobbly Dialogue Bubble Demo (HTML, CSS, & JS)

I was inspired by Animal Crossing: New Horizons' wobbly dialogue boxes, animations, and animalese, so I made a thing simulating it in HTML, CSS, and JS!

I may improve it more later with dynamic speech sound + timing.

Credits:

  1. Josh Simmons / Acedio for the animalese sound generator used to produce the speech for this: https://github.com/Acedio/animalese.js

  2. Zombirk for the lovely wallpaper background video (embedded): https://www.youtube.com/channel/UCyv1QgsbBopSIgBlQnt2qAg

A Pen by Andy Merskin on CodePen.

License.

<div id="app">
<audio src="https://srv-file16.gofile.io/download/N0MPla/animalese.mp3" ref="audio"></audio>
<iframe class="video" width="100%" height="100%" src="https://www.youtube.com/embed/v3ZGztm82n0?autoplay=1" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<dialogue>
<span slot="character">Tom Nook</span>
What a great night to watch the shooting stars. You should try making a wish and see what happens!
</dialogue>
</div>
<script type="text/x-template" id="dialogue">
<div class="dialogue">
<div class="dialogue-blobs">
<div class="dialogue-blob-top"></div>
<div class="dialogue-blob-bottom"></div>
<dialogue-text>
<slot></slot>
</dialogue-text>
</div>
<div class="dialogue-character-wrap">
<div class="dialogue-character">
<slot name="character"></slot>
</div>
</div>
<svg class="arrow" width="45" height="25" viewBox="0 0 45 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M22.5 25C18.0184 25 7.59473 12.6404 1.55317 4.96431C-0.122281 2.83559 1.72264 -0.179893 4.39835 0.243337C10.2831 1.17415 18.2164 2.28736 22.5 2.28736C26.7836 2.28736 34.7169 1.17415 40.6017 0.243339C43.2774 -0.17989 45.1223 2.83559 43.4468 4.96431C37.4053 12.6404 26.9816 25 22.5 25Z" fill="#F1AE04"/>
</svg>
</div>
</script>
<script type="text/x-template" id="dialogue-text">
<div class="dialogue-text">
{{displayedText}}
</div>
</script>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<defs>
<filter id="old-goo">
<feGaussianBlur in="SourceGraphic" stdDeviation="10" result="blur" />
<feColorMatrix in="blur" mode="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 18 -7" result="goo" />
<feBlend in="SourceGraphic" in2="goo" />
</filter>
<filter id="fancy-goo">
<feGaussianBlur in="SourceGraphic" stdDeviation="10" result="blur" />
<feColorMatrix in="blur" mode="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 19 -9" result="goo" />
<feComposite in="SourceGraphic" in2="goo" operator="atop" />
</filter>
</defs>
</svg>
Vue.config.devtools = true;
Vue.component("dialogue", {
template: "#dialogue"
});
Vue.component("dialogue-text", {
template: "#dialogue-text",
data() {
return {
text: "",
displayedText: ""
};
},
created() {
this.text = this.$slots.default[0].text;
},
mounted() {
const speed = 25;
const delay = 2000;
let i = 0;
const typewriter = () => {
if (i < this.text.length) {
this.displayedText += this.text.charAt(i);
i++;
setTimeout(typewriter, speed);
}
};
setTimeout(typewriter, delay);
}
});
const app = new Vue({
el: "#app",
mounted() {
setTimeout(() => {
this.$refs.audio.play();
}, 2000);
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.min.js"></script>
$sans: "Hind", sans-serif;
$round: "Varela Round", sans-serif;
$radius: 9999px;
$easeInOutSine: cubic-bezier(0.37, 0, 0.63, 1);
$easeOutBack: cubic-bezier(0.34, 1.56, 0.64, 1);
$easeInQuint: cubic-bezier(0.64, 0, 0.78, 0);
* {
box-sizing: border-box;
}
html,
body {
height: 100vh;
overflow: hidden;
}
body {
background-color: black;
// background: url("https://assets-prd.ignimgs.com/2020/02/20/ac3-1582208530734.jpg");
// background-repeat: no-repeat;
// background-position: bottom center;
// background-size: cover;
}
#app {
display: flex;
justify-content: center;
align-items: flex-end;
padding-bottom: 3rem;
height: 100vh;
}
.video {
position: absolute;
top: 0;
left: 0;
}
.dialogue {
position: relative;
display: flex;
max-height: percentage(1/2);
min-height: 300px;
min-width: 1024px;
width: 60%;
}
.dialogue-blobs {
position: relative;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: stretch;
filter: url("#fancy-goo");
transform: scale(0);
animation: scale-up 0.6s $easeOutBack 1s 1 normal forwards;
}
.dialogue-blob-top {
position: absolute;
top: 0;
width: 100%;
height: 75%;
background-color: #fdf8e3;
border-radius: 40% 40% 30% 30% / 150% 150% 150% 150%;
animation: blob 1.5s $easeInOutSine 0.3s infinite alternate;
transform-origin: center;
}
.dialogue-blob-bottom {
position: absolute;
bottom: 0;
width: 94%;
height: 40%;
background-color: #fdf8e3;
border-radius: 5% 5% 20% 20% / 100% 100% 100% 100%;
animation: blob 1s infinite alternate $easeInOutSine;
transform-origin: center;
}
.dialogue-character-wrap {
position: absolute;
animation: character 0.6s infinite alternate $easeInOutSine;
}
.dialogue-character {
display: inline-block;
margin-right: auto;
padding: 0.5rem 2rem;
font-family: $round;
font-size: 2rem;
color: #482016;
background-color: #dd8530;
border-radius: 30% / 100% 100% 120% 120%;
transform: perspective(2rem) rotateX(1deg) rotateZ(-9deg) translateX(20%)
translateY(-45%) scale(0);
animation: fade-character 0.3s $easeOutBack 1s 1 normal forwards;
}
.dialogue-text {
position: absolute;
width: 100%;
padding: 1em 1em 2em 1.5em;
font-family: $sans;
font-size: 3rem;
line-height: 1.5em;
color: #807256;
}
.arrow {
position: absolute;
bottom: 0;
left: 50%;
opacity: 0;
animation: arrow 0.6s $easeInOutSine 4.5s infinite alternate;
}
@keyframes blob {
from {
transform: rotate(0.3deg) scale(1);
}
to {
transform: rotate(-0.3deg) scale(0.99);
}
}
@keyframes character {
from {
transform: translateY(0);
}
to {
transform: translateY(3px);
}
}
@keyframes scale-up {
0% {
transform: scale(0.8);
opacity: 0;
}
49% {
}
50% {
}
to {
transform: scale(1);
opacity: 1;
}
}
@keyframes fade-character {
from {
transform: perspective(2rem) rotateX(1deg) rotateZ(0deg) translateX(20%)
translateY(-45%) scale(0.8);
opacity: 0;
}
to {
transform: perspective(2rem) rotateX(1deg) rotateZ(-6deg) translateX(20%)
translateY(-45%) scale(1);
opacity: 1;
}
}
@keyframes arrow {
from {
transform: translateY(33%) translateX(-50%) scale(1);
opacity: 1;
}
to {
transform: translateY(50%) translateX(-50%) scale(0.9);
opacity: 1;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment