Created
July 27, 2025 15:28
-
-
Save thinkphp/c6f861f459ec9457a7a49c28df4fd771 to your computer and use it in GitHub Desktop.
fab web component
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class ContactFab extends HTMLElement { | |
constructor() { | |
super(); | |
this.attachShadow({ mode: 'open' }); | |
this.isOpen = false; | |
// Get attributes or set defaults | |
this.whatsappNumber = this.getAttribute('whatsapp') || '+1234567890'; | |
this.emailAddress = this.getAttribute('email') || '[email protected]'; | |
this.phoneNumber = this.getAttribute('phone') || '+1234567890'; | |
this.instagramUsername = this.getAttribute('instagram') || 'instagram'; | |
this.render(); | |
this.attachEventListeners(); | |
} | |
render() { | |
this.shadowRoot.innerHTML = ` | |
<style> | |
:host { | |
position: fixed; | |
bottom: 30px; | |
right: 30px; | |
z-index: 1000; | |
font-family: Arial, sans-serif; | |
} | |
.fab-main { | |
width: 60px; | |
height: 60px; | |
background: linear-gradient(45deg, #25D366, #128C7E); | |
border-radius: 50%; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
cursor: pointer; | |
box-shadow: 0 4px 20px rgba(37, 211, 102, 0.4); | |
transition: all 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55); | |
border: none; | |
outline: none; | |
} | |
.fab-main:hover { | |
transform: scale(1.1); | |
box-shadow: 0 6px 25px rgba(37, 211, 102, 0.6); | |
} | |
.fab-main.active { | |
transform: rotate(45deg); | |
background: linear-gradient(45deg, #ff4757, #ff3742); | |
box-shadow: 0 6px 25px rgba(255, 71, 87, 0.6); | |
} | |
.fab-icon { | |
width: 24px; | |
height: 24px; | |
fill: white; | |
transition: transform 0.3s ease; | |
} | |
.fab-main.active .fab-icon { | |
transform: rotate(-45deg); | |
} | |
.fab-options { | |
position: absolute; | |
bottom: 80px; | |
right: 0; | |
display: flex; | |
flex-direction: column; | |
gap: 15px; | |
opacity: 0; | |
visibility: hidden; | |
transform: translateY(20px); | |
transition: all 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55); | |
} | |
.fab-options.active { | |
opacity: 1; | |
visibility: visible; | |
transform: translateY(0); | |
} | |
.fab-option { | |
width: 50px; | |
height: 50px; | |
border-radius: 50%; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
text-decoration: none; | |
color: white; | |
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); | |
transition: all 0.3s ease; | |
position: relative; | |
overflow: hidden; | |
} | |
.fab-option::before { | |
content: ''; | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
background: linear-gradient(45deg, rgba(255,255,255,0.1), rgba(255,255,255,0.3)); | |
opacity: 0; | |
transition: opacity 0.3s ease; | |
} | |
.fab-option:hover::before { | |
opacity: 1; | |
} | |
.fab-option:hover { | |
transform: scale(1.1); | |
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3); | |
} | |
.whatsapp { | |
background: linear-gradient(45deg, #25D366, #128C7E); | |
animation-delay: 0.1s; | |
} | |
.email { | |
background: linear-gradient(45deg, #EA4335, #D33B2C); | |
animation-delay: 0.2s; | |
} | |
.phone { | |
background: linear-gradient(45deg, #4285F4, #1a73e8); | |
animation-delay: 0.3s; | |
} | |
.instagram { | |
background: linear-gradient(45deg, #00BCD4, #0097A7); /* Cyan */ | |
animation-delay: 0.4s; | |
} | |
.fab-option-icon { | |
width: 20px; | |
height: 20px; | |
fill: white; | |
} | |
.fab-tooltip { | |
position: absolute; | |
right: 60px; | |
top: 50%; | |
transform: translateY(-50%); | |
background: rgba(0, 0, 0, 0.8); | |
color: white; | |
padding: 8px 12px; | |
border-radius: 20px; | |
font-size: 14px; | |
white-space: nowrap; | |
opacity: 0; | |
visibility: hidden; | |
transition: all 0.3s ease; | |
pointer-events: none; | |
} | |
.fab-tooltip::after { | |
content: ''; | |
position: absolute; | |
left: 100%; | |
top: 50%; | |
transform: translateY(-50%); | |
border: 6px solid transparent; | |
border-left-color: rgba(0, 0, 0, 0.8); | |
} | |
.fab-option:hover .fab-tooltip { | |
opacity: 1; | |
visibility: visible; | |
} | |
@keyframes bounce { | |
0%, 100% { transform: translateY(0); } | |
50% { transform: translateY(-10px); } | |
} | |
.fab-options.active .fab-option { | |
animation: bounce 0.6s ease forwards; | |
} | |
/* Mobile responsiveness */ | |
@media (max-width: 768px) { | |
:host { | |
bottom: 20px; | |
right: 20px; | |
} | |
.fab-main { | |
width: 55px; | |
height: 55px; | |
} | |
.fab-option { | |
width: 45px; | |
height: 45px; | |
} | |
.fab-tooltip { | |
font-size: 12px; | |
padding: 6px 10px; | |
} | |
} | |
</style> | |
<div class="contact-fab"> | |
<button class="fab-main" id="fabMain"> | |
<svg class="fab-icon" viewBox="0 0 24 24"> | |
<path d="M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z" /> | |
</svg> | |
</button> | |
<div class="fab-options" id="fabOptions"> | |
<a href="https://wa.me/${this.whatsappNumber.replace(/\D/g, '')}" class="fab-option whatsapp" target="_blank"> | |
<svg class="fab-option-icon" viewBox="0 0 24 24"> | |
<path d="M17.472,14.382c-0.297-0.149-1.758-0.867-2.03-0.967c-0.273-0.099-0.471-0.148-0.67,0.15c-0.197,0.297-0.767,0.966-0.94,1.164c-0.173,0.199-0.347,0.223-0.644,0.075c-0.297-0.15-1.255-0.463-2.39-1.475c-0.883-0.788-1.48-1.761-1.653-2.059c-0.173-0.297-0.018-0.458,0.13-0.606c0.134-0.133,0.297-0.347,0.446-0.52C9.87,9.97,9.919,9.846,10.019,9.65c0.099-0.198,0.05-0.371-0.025-0.52C9.919,8.981,9.325,7.515,9.078,6.92c-0.241-0.58-0.487-0.5-0.669-0.51c-0.173-0.008-0.371-0.01-0.57-0.01c-0.198,0-0.52,0.074-0.792,0.372c-0.272,0.297-1.04,1.016-1.04,2.479c0,1.462,1.065,2.875,1.213,3.074c0.149,0.198,2.096,3.2,5.077,4.487c0.709,0.306,1.262,0.489,1.694,0.625c0.712,0.227,1.36,0.195,1.871,0.118c0.571-0.085,1.758-0.719,2.006-1.413c0.248-0.694,0.248-1.289,0.173-1.413C17.944,14.653,17.769,14.531,17.472,14.382z M12.057,21.785h-0.008c-1.805,0-3.573-0.488-5.126-1.411l-0.368-0.218l-3.815,1.002l1.018-3.721l-0.238-0.382C2.438,15.25,1.898,13.688,1.898,12.057c0-5.561,4.526-10.086,10.091-10.086c2.693,0,5.225,1.05,7.127,2.953c1.902,1.903,2.951,4.435,2.951,7.128C22.067,17.613,17.541,22.14,12.057,21.785z"/> | |
</svg> | |
<div class="fab-tooltip">WhatsApp</div> | |
</a> | |
<a href="mailto:${this.emailAddress}" class="fab-option email"> | |
<svg class="fab-option-icon" viewBox="0 0 24 24"> | |
<path d="M20,8L12,13L4,8V6L12,11L20,6M20,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V6C22,4.89 21.1,4 20,4Z" /> | |
</svg> | |
<div class="fab-tooltip">Email Us</div> | |
</a> | |
<a href="tel:${this.phoneNumber}" class="fab-option phone"> | |
<svg class="fab-option-icon" viewBox="0 0 24 24"> | |
<path d="M6.62,10.79C8.06,13.62 10.38,15.94 13.21,17.38L15.41,15.18C15.69,14.9 16.08,14.82 16.43,14.93C17.55,15.3 18.75,15.5 20,15.5A1,1 0 0,1 21,16.5V20A1,1 0 0,1 20,21A17,17 0 0,1 3,4A1,1 0 0,1 4,3H7.5A1,1 0 0,1 8.5,4C8.5,5.25 8.7,6.45 9.07,7.57C9.18,7.92 9.1,8.31 8.82,8.59L6.62,10.79Z" /> | |
</svg> | |
<div class="fab-tooltip">Call Us</div> | |
</a> | |
<a href="https://instagram.com/${this.instagramUsername}" class="fab-option instagram" target="_blank"> | |
<svg class="fab-option-icon" viewBox="0 0 24 24"> | |
<path d="M7.8,2H16.2C19.4,2 22,4.6 22,7.8V16.2A5.8,5.8 0 0,1 16.2,22H7.8C4.6,22 2,19.4 2,16.2V7.8A5.8,5.8 0 0,1 7.8,2M7.6,4A3.6,3.6 0 0,0 4,7.6V16.4C4,18.39 5.61,20 7.6,20H16.4A3.6,3.6 0 0,0 20,16.4V7.6C20,5.61 18.39,4 16.4,4H7.6M17.25,5.5A1.25,1.25 0 0,1 18.5,6.75A1.25,1.25 0 0,1 17.25,8A1.25,1.25 0 0,1 16,6.75A1.25,1.25 0 0,1 17.25,5.5M12,7A5,5 0 0,1 17,12A5,5 0 0,1 12,17A5,5 0 0,1 7,12A5,5 0 0,1 12,7M12,9A3,3 0 0,0 9,12A3,3 0 0,0 12,15A3,3 0 0,0 15,12A3,3 0 0,0 12,9Z" /> | |
</svg> | |
<div class="fab-tooltip">Instagram</div> | |
</a> | |
</div> | |
</div> | |
`; | |
} | |
attachEventListeners() { | |
const fabMain = this.shadowRoot.getElementById('fabMain'); | |
const fabOptions = this.shadowRoot.getElementById('fabOptions'); | |
fabMain.addEventListener('click', (e) => { | |
e.stopPropagation(); | |
this.toggleFab(); | |
}); | |
// Close FAB when clicking outside | |
document.addEventListener('click', (e) => { | |
if (!e.composedPath().includes(this)) { | |
this.closeFab(); | |
} | |
}); | |
} | |
toggleFab() { | |
this.isOpen = !this.isOpen; | |
const fabMain = this.shadowRoot.getElementById('fabMain'); | |
const fabOptions = this.shadowRoot.getElementById('fabOptions'); | |
if (this.isOpen) { | |
fabMain.classList.add('active'); | |
fabOptions.classList.add('active'); | |
} else { | |
fabMain.classList.remove('active'); | |
fabOptions.classList.remove('active'); | |
} | |
} | |
closeFab() { | |
if (this.isOpen) { | |
this.isOpen = false; | |
const fabMain = this.shadowRoot.getElementById('fabMain'); | |
const fabOptions = this.shadowRoot.getElementById('fabOptions'); | |
fabMain.classList.remove('active'); | |
fabOptions.classList.remove('active'); | |
} | |
} | |
static get observedAttributes() { | |
return ['whatsapp', 'email', 'phone', 'instagram']; | |
} | |
attributeChangedCallback(name, oldValue, newValue) { | |
if (oldValue !== newValue) { | |
if (name === 'whatsapp') { | |
this.whatsappNumber = newValue; | |
} else if (name === 'email') { | |
this.emailAddress = newValue; | |
} else if (name === 'phone') { | |
this.phoneNumber = newValue; | |
} else if (name === 'instagram') { | |
this.instagramUsername = newValue; | |
} | |
this.render(); | |
this.attachEventListeners(); | |
} | |
} | |
} | |
customElements.define('contact-fab', ContactFab); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment