<template> <div class="tooltip-container"> <div ref="trigger" class="trigger noselect" @click.stop="toggle" > <slot /> </div> <teleport to="body"> <div v-if="opened" ref="tooltip" class="tooltip-wrapper" :style="{ transform: `translate3d(${tooltipX}px, ${tooltipY}px, 0)` }" > <div class="tooltip-inner"> <div class="tooltip"> <slot name="tooltip" /> </div> </div> </div> </teleport> </div> </template> <script> import { nextTick } from 'vue'; function getX(triggerBoundary, tooltipBoundary) { const idealPosition = triggerBoundary.left + (triggerBoundary.width / 2) - (tooltipBoundary.width / 2); // don't overflow left edge if (idealPosition < 0) { return 0; } // don't overflow right edge if (idealPosition + tooltipBoundary.width > window.innerWidth) { return window.innerWidth - tooltipBoundary.width; } // position at the center of trigger return idealPosition; } async function calculate() { if (!this.opened) { return; } const triggerBoundary = this.$refs.trigger.getBoundingClientRect(); const tooltipBoundary = this.$refs.tooltip.getBoundingClientRect(); this.tooltipY = triggerBoundary.top + triggerBoundary.height; this.tooltipX = this.getX(triggerBoundary, tooltipBoundary); } async function open() { this.events.emit('blur'); await nextTick(); this.opened = true; await nextTick(); this.calculate(); } function close() { this.opened = false; this.tooltipY = 0; this.tooltipX = 0; } function toggle() { if (this.opened) { this.close(); return; } this.open(); } function mounted() { this.events.on('blur', () => { this.close(); }); this.events.on('resize', () => { this.calculate(); }); } export default { data() { return { opened: false, tooltipX: 0, tooltipY: 0, }; }, mounted, methods: { calculate, getX, open, close, toggle, }, }; </script> <style lang="scss" scoped> .tooltip-wrapper { display: flex; top: 0; left: 0; flex-direction: column; justify-content: center; position: absolute; z-index: 10; } .tooltip-inner { position: relative; box-shadow: 0 0 3px var(--darken-weak); &:after { content: ''; width: 0; height: 0; position: absolute; top: -.5rem; left: calc(50% - .5rem); border-left: .5rem solid transparent; border-right: .5rem solid transparent; border-bottom: .5rem solid var(--background-light); margin: 0 auto; filter: drop-shadow(0 0 3px var(--darken-weak)); } } .tooltip { position: relative; background: var(--background-light); } </style>