Test
- chat-container
#chat-input #file-input <style type="text/css" media="screen">
html, body { background-color: hsl(0, 0%, 8%); // margin: 0; // Go fixed // font-size: 0; // Fix white-space issues if present. // height: 100vh; // Go fixed user-select: none; }
// For centering
- chat-container {
display: flex; justify-content: center; align-items: center; }
- chat-input {
position: fixed; bottom: 10px; height: 40px; width: 500px; background-color: hsl(0, 0%, 14%); border-radius: 6px; overflow: hidden;
&:before, &:after { content: ; display: block; position: absolute; top: 12px; bottom: 12px; background-color: hsla(0, 0%, 22%, 0.4); } // Chat input &:before { cursor: text; left: 52px; width: 40%; border-radius: 2px; } // Emote picker &:after { cursor: pointer; right: 10px; width: 16px; border-radius: 8px; } #file-input { cursor: pointer; display: block; border-right: 2px solid hsl(0, 0%, 16%); position: absolute; top: 2px; left: 2px; bottom: 2px; width: 36px; transition: background-color 60ms;
&:hover { top: 0; left: 0; bottom: 0; width: 40px; background-color: hsl(0, 0%, 28%); border-right: 0; transition: background-color 120ms; } &:before, &:after { content: ; display: block; position: absolute; background-color: hsl(0, 0%, 22%); } &:before { top: 30%; bottom: 30%; left: 50%; width: 2px; margin-left: -1px; } &:after { left: 30%; right: 30%; top: 50%; height: 2px; margin-top: -1px; } } }
.chat { position: fixed; bottom: 60px; width: 500px; display: inline-block; }
.line-container { overflow: hidden; border-radius: 6px; max-height: 0px; opacity: 0; transform: translateX(-300px) scale(0.2); transition: margin-bottom 200ms, max-height 500ms, opacity 100ms, transform 250ms; transition-timing-function: ease-out;
&:not(:last-child) {
margin-bottom: 10px;
}
}
.line { padding: 10px; background-color: hsl(0, 0%, 14%);
& > div { display: inline-block; vertical-align: top; } }
.profile-img { cursor: pointer; border-radius: 6px; width: 60px; height: 60px; background-color: hsl(0, 0%, 22%); margin-right: 10px; }
.body { // background-color: black;
.name, .text { border-radius: 2px; background-color: hsl(0, 0%, 28%); height: 16px; } }
.name { width: 100px; margin-bottom: 10px; position: relative; cursor: pointer;
&:after { content: ; display: block; border-radius: 2px; background-color: hsla(0, 0%, 22%, 0.4); height: 16px; width: 50px; position: absolute; right: -60px; transition: 100ms; } }
.profile-img:hover + .body .name:after, .name:hover:after { background-color: hsla(0, 0%, 22%, 1); width: 100px; right: -110px; }
.text { &:not(:last-child) { margin-bottom: 10px; } }
.img { }
.rich-body { margin-left: 14px; margin-top: 36px; position: relative;
&:before { content: ; display: block; position: absolute; top: -26px; left: -14px; bottom: 0; width: 4px; background-color: inherit; } &:after { content: ; display: block; position: absolute; height: 16px; width: 200px; top: -26px; background-color: inherit; border-radius: 2px; } }
.img, .rich-body { width: 300px; height: 300px; cursor: pointer; border-radius: 6px; background-color: hsl(0, 0%, 20%); }
.profile-img, .name, .text, .img, .rich-body { opacity: 0; transform: translateY(20px); transition: 200ms; } </style> <style type="text/javascript" media="screen">
let amountOfColors = 18; // Or "participants"
let container = document.getElementById('chat-container'); let lineWidth = 500; let profileImgWidth = 60; let textWidth = lineWidth - 20 - profileImgWidth - 10; let chats = []; let maxTexts = 4;
function createElement(opts = {}) { let ele = document.createElement('div'); if('class' in opts) { if(!Array.isArray(opts.class)) { opts.class = [ opts.class ]; } ele.classList.add(...opts.class); } return ele; }
function addChat() { let chat = new Chat(); chats.push(chat); setTimeout(() => chat.loop(), 200); return chat; }
class Chat { constructor() { this.ele = createElement({ class: 'chat' }); this.lines = []; this.anim = null; container.appendChild(this.ele); } addLine() { let l = new Line(); this.lines.push(l); this.ele.appendChild(l.ele.lineContainer); return l; } removeOldest() { let maxCount = Math.ceil(window.innerHeight / 1080 * 12); if(this.lines.length > maxCount) { let oldest = this.lines.splice(0, this.lines.length - maxCount); oldest.forEach(n => this.ele.removeChild(n.ele.lineContainer)); } } loop() { if(this.anim) { this.stopLoop(); } this.addLine(); this.removeOldest(); this.anim = setTimeout(() => this.loop(), Math.random() * 1300 + 180); } stopLoop() { clearTimeout(this.anim); this.anim = null; } }
class Line { constructor() { this.pickColor(); this.pickName(); this.pickText(); this.pickHasImg(); this.pickHasRichBody(); this.setupElements(); this.animateIn(); }
pickColor() { this.hue = Math.floor(Math.random() * amountOfColors) * (360 / amountOfColors); this.color = `hsl(${this.hue}, 90%, 50%)`; this.profileImgColor = `hsl(${this.hue}, 40%, 55%)`; return this.hue; }
pickName() { this.name = Math.max(0.3, Math.random()); }
pickText() { let lengthChoice = Math.random(); let lengthWeight = 1; if(lengthChoice < 0.5) { lengthWeight = 0.6; } else if(lengthChoice < 0.9) { lengthWeight = 0.8; } this.length = Math.max(0.02, lengthChoice * lengthWeight); this.textCount = this.length * maxTexts; }
pickHasImg() { this.hasImg = Math.random() > 0.9; }
pickHasRichBody() { this.hasRichBody = !this.hasImage && Math.random() > 0.85; }
setupElements() { let ele = this.createElement(); this.ele = ele; ele.name.style.width = this.name * (textWidth / 2) + 'px'; ele.texts.forEach((n, i, arr) => { let w = textWidth; if(i === arr.length - 1) { w = Math.max(0.2, (this.textCount - i)) * textWidth; } n.style.width = w + 'px'; }); ele.name.style.backgroundColor = this.color; ele.profileImg.style.backgroundColor = this.profileImgColor; }
animateIn() { let delay = 35; // Some times it won't animate correctly without this let ele = this.ele; setTimeout(() => { ele.lineContainer.style.opacity = 1; ele.lineContainer.style.maxHeight = '200px'; ele.lineContainer.style.transform = 'translateX(0px) scale(1)'; }, delay);
let otherEleList = [ ele.profileImg, ele.name, ...ele.texts ];
if('img' in ele) { otherEleList.push(ele.img); } else if('richBody' in ele) { otherEleList.push(ele.richBody); }
delay += 40;
otherEleList.forEach((e, i) => { setTimeout(() => { e.style.opacity = 1; e.style.transform = 'translateY(0px)'; }, delay += 50); });
ele.texts.forEach((n, i, arr) => setTimeout(() => n.style.opacity = 1, 70 * (i + 3) + delay)); }
createElement() { let lineContainer = createElement({ class: 'line-container' }); let line = createElement({ class: 'line' }); let profileImg = createElement({ class: 'profile-img' }); let body = createElement({ class: 'body' }); let name = createElement({ class: 'name' }); let texts = []; let img = createElement({ class: 'img' }); let richBody = createElement({ class: 'rich-body' }); body.appendChild(name); for(let i = 0; i < (this.textCount || 1); i++) { let text = createElement({ class: 'text' }); texts.push(text); body.appendChild(text); } line.appendChild(profileImg); line.appendChild(body); lineContainer.appendChild(line); let out = { lineContainer, line, profileImg, body, name, texts }; this.hasImg && (out.img = img) && body.appendChild(img); this.hasRichBody && (out.richBody = richBody) && body.appendChild(richBody); return out; } }
function loop() { chats.forEach(n => n.loop()); }
function stopLoop() { chats.forEach(n => n.stopLoop()); }
(() => addChat())();
</style>