What is Popper?
Popper identifies itself as a TOOLTIP & POPOVER POSITIONING ENGINE
. It basically helps your popovers and tooltips position properly. Popper is awesome and used by millions and giants (according to their website) such as Microsoft, Atlassian, GitLab, etc. And I use it at work and while working on my side projects.
Okay, but with Svelte we can use any tools or libraries without an extra work or binding
Yes but it doesn't mean that we can't improve our codebase.
Yep, you're right, so how to use it?
First, I want to show you how you can use it without Sveltish
way. Let's create a new Svelte project with:
npx degit sveltejs/template my-svelte-project
cd sveltishpopper
npm i
Then install Popper
npm i @popperjs/core
Then open src/App.svelte
with your favorite editor, and delete everything.
Create a file named Modal.svelte
, then paste following:
<style>
#modal {
display: flex;
justify-content: center;
align-items: center;
height: 40vh;
width: 20vw;
flex-direction: column;
background-color: wheat;
border-radius: 1em;
}
#modal > img {
margin-top: 3em;
}
</style>
<div id="modal">
Hello Popper!
<img src="https://popper.js.org/static/popper-logo-394b4ea5914aad7fc580f418ed0cfb17.svg" alt="Popper logo">
</div>
It's just a basic modal with a background color.
Now open App.svelte
and paste
<script>
import { onMount } from 'svelte';
import { createPopper } from '@popperjs/core/dist/esm';
import Modal from './Modal.svelte';
let popButton, modal, isModalActive = false;
const toggleModal = () => {
isModalActive = !isModalActive;
};
onMount(() => {
createPopper(popButton, modal);
});
</script>
<button bind:this={popButton} on:click={toggleModal}>
Pop it
</button>
{#if isModalActive}
<Modal bind:this={modal} />
{/if}
In the code above, nothing really challenging goes on
And congratz! You got yourself a functioning modal with awesome positioning.
This implementation is really simple but everytime you want to use a modal with popper, you need to repeat this implementation. How can we improve?
Action!
Now create a file named, actually it doesn't really matter because this isn't really a project but let's call it, PopperAction.js
.
And paste following:
import { createPopper } from '@popperjs/core/dist/esm';
export function popover(node, { component, ...props }) {
const button = node;
let popperInstance, componentInstance, renderedComponent, isActive = false;
const id = 'modal';
const toggle = e => {
e.stopPropagation()
isActive ? hide() : show();
};
button.addEventListener('click', toggle);
const detectClickOutside = event => {
if (renderedComponent && !renderedComponent.contains(event.target) && isActive) {
hide();
}
};
const show = () => {
componentInstance = new component({
target: document.body,
props
});
isActive = true;
renderedComponent = document.querySelector(`#${id}`);
popperInstance = createPopper(button, renderedComponent, {
modifiers: [
{
name: 'offset',
options: {
offset: [0, 8]
}
}
]
});
document.addEventListener('click', detectClickOutside);
};
const hide = () => {
renderedComponent = document.querySelector(`#${id}`);
isActive = false;
if (popperInstance) {
popperInstance.destroy();
popperInstance = null;
}
componentInstance.$destroy();
document.removeEventListener('click', detectClickOutside);
}
return {
destroy() {
button.removeEventListener('click', toggle);
document.removeEventListener('click', detectClickOutside);
}
}
}
Okay, what the heck??
This implementation is more confusing but let me explain what's happening down there. (You can skip if you understood what it does.)
So, we are defining a function that takes, a node and some props. We add a click event listener to the node (in this case its a button) and we bind it to a toggle function, which toggles the modal (eh?).
Show function is creating a popper instance every time we click to the button, and hide is hiding the modal and destroying the popper instance. You can figure out the optimizations yourselves, I'm in a rush!
As a bonus I ended up adding a click outside handler which detects clicks that are outside of the modal.
We return an object from the action.
return {
destroy() {
button.removeEventListener('click', toggle);
document.removeEventListener('click', detectClickOutside);
}
}
It has a special method named destroy
, it's duty is to clean up effects ( :) ).
Hmm, seems legit, but how to use it?
That's the awesome part. Get ready to shocked in 3, 2, 1...
<script>
import { popover } from './PopperAction.js';
import Modal from './Modal.svelte';
</script>
<button use:popover={{ component: Modal }}>
Pop it
</button>
Look how beautiful it is. 😢
Okay this is it. Have a nice day.
PS, checkout official svelte package of popper
Top comments (1)
thank you for your post, i have a question, how to get event from inside Modal ?
how to bind data from outside to modal if using popover
thank you!