
It supports four directions (top, right, bottom, left) and helps you display content like navigation menus, sidebar panels or cookie consent banners compactly.
Features:
- CSS keyframe-based transitions with easing controls
- Responsive sizing via Tailwind utility classes
- Overlay dimming effect with fade transitions
- Built-in keyboard and screen reader accessibility
- Customizable animation durations and delays
See
it in action:
How to use it:
1. Code the HTML structure for your panel elements. The script will use .panel class and the data-direction attribute to determine which animation to use. Here are examples for all four directions. Note that the styling uses TailwindCSS, but you can replace those with your own classes.
<!-- Top To Bottom -->
<div id="panel-top" class="panel" data-direction="top">
<div class="panel-content max-w-2xl mx-auto text-center">
... Any Content Here ...
<button class="close-btn bg-purple-600 hover:bg-purple-700 text-white font-bold py-2 px-4 rounded">Close</button>
</div>
</div>
<!-- Right To Left -->
<div id="panel-right" class="panel" data-direction="right">
<div class="panel-content flex flex-col h-full">
... Anyt Content Here ...
<div class="mt-auto">
<button class="close-btn w-full bg-green-600 hover:bg-green-700 text-white font-bold py-3 px-4 rounded">Close</button>
</div>
</div>
</div>
<!-- Bottom to Top -->
<div id="panel-bottom" class="panel bg-gray-800 text-white" data-direction="bottom">
<div class="panel-content max-w-4xl mx-auto flex flex-col sm:flex-row items-center justify-between text-center sm:text-left">
... Any Content Here ...
<button class="close-btn bg-red-600 hover:bg-red-700 text-white font-bold py-2 px-6 rounded-lg flex-shrink-0">Close</button>
</div>
</div>
<!-- Left to Right -->
<div id="panel-left" class="panel" data-direction="left">
<div class="panel-content flex flex-col h-full">
... Any Content Here ...
<div class="mt-auto">
<button class="close-btn w-full bg-gray-200 hover:bg-gray-300 text-gray-800 font-bold py-3 px-4 rounded">Close</button>
</div>
</div>
</div>
2. Create the buttons that will open each panel. The data-target-panel attribute must match the id of the panel you want to open.
<button data-target-panel="panel-top" class="bg-gradient-to-br from-purple-500 to-indigo-600 text-white font-bold py-3 px-6 rounded-lg shadow-lg transform hover:scale-105 transition-transform duration-200"> From Top </button> <button data-target-panel="panel-right" class="bg-gradient-to-br from-green-500 to-teal-600 text-white font-bold py-3 px-6 rounded-lg shadow-lg transform hover:scale-105 transition-transform duration-200"> From Right </button> <button data-target-panel="panel-bottom" class="bg-gradient-to-br from-orange-500 to-red-600 text-white font-bold py-3 px-6 rounded-lg shadow-lg transform hover:scale-105 transition-transform duration-200"> From Bottom </button> <button data-target-panel="panel-left" class="bg-gradient-to-br from-blue-500 to-cyan-600 text-white font-bold py-3 px-6 rounded-lg shadow-lg transform hover:scale-105 transition-transform duration-200"> From Left </button>
3. Create an overlay element that dims the whole page when the panel is opened.
<div id="overlay" class="overlay"></div>
4. The necessary CSS/CSS3. It defines the slide-in and slide-out animations using @keyframes, styles the base panel and overlay, and uses helper classes to apply the animations.
- Panel Animations:
slideInFromRight,slideOutToRight(and variants for all directions) - Panel States:
.panel.is-openfor visibility control - Overlay States:
.overlay.is-openfor backdrop visibility - Content Animations: Staggered fade-in effects for panel children
/* --- Keyframe Animations for Panel Sliding --- */
@keyframes slideInFromRight {
from {
transform: translateX(100%);
}
to {
transform: translateX(0);
}
}
@keyframes slideOutToRight {
from {
transform: translateX(0);
}
to {
transform: translateX(100%);
}
}
@keyframes slideInFromLeft {
from {
transform: translateX(-100%);
}
to {
transform: translateX(0);
}
}
@keyframes slideOutToLeft {
from {
transform: translateX(0);
}
to {
transform: translateX(-100%);
}
}
@keyframes slideInFromTop {
from {
transform: translateY(-100%);
}
to {
transform: translateY(0);
}
}
@keyframes slideOutToTop {
from {
transform: translateY(0);
}
to {
transform: translateY(-100%);
}
}
@keyframes slideInFromBottom {
from {
transform: translateY(100%);
}
to {
transform: translateY(0);
}
}
@keyframes slideOutToBottom {
from {
transform: translateY(0);
}
to {
transform: translateY(100%);
}
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* --- Panel Base Styling --- */
.panel {
position: fixed;
background-color: white;
box-shadow: 0 0 30px rgba(0, 0, 0, 0.2);
z-index: 1000;
visibility: hidden;
/* Hidden by default */
}
.panel.is-open {
visibility: visible;
}
.panel.is-open .panel-content>* {
animation: fadeIn 0.5s both;
}
.panel.is-open .panel-content>*:nth-child(1) {
animation-delay: 0.3s;
}
.panel.is-open .panel-content>*:nth-child(2) {
animation-delay: 0.4s;
}
.panel.is-open .panel-content>*:nth-child(3) {
animation-delay: 0.5s;
}
.panel.is-open .panel-content>*:nth-child(4) {
animation-delay: 0.6s;
}
.panel.is-open .panel-content>*:nth-child(5) {
animation-delay: 0.7s;
}
/* --- Overlay Styling --- */
.overlay {
position: fixed;
inset: 0;
/* Shorthand for top: 0; right: 0; bottom: 0; left: 0; */
background-color: rgba(0, 0, 0, 0.6);
/* Semi-transparent black */
z-index: 999;
/* Below panels */
opacity: 0;
/* Hidden by default */
visibility: hidden;
/* Hidden by default */
transition: opacity 0.4s ease-out, visibility 0.4s ease-out;
/* Smooth transition */
}
.overlay.is-open {
opacity: 1;
visibility: visible;
}
/* --- Panel Animation Classes --- */
.panel.animate-in-right {
animation: slideInFromRight 0.4s forwards ease-out;
}
.panel.animate-out-right {
animation: slideOutToRight 0.3s forwards ease-in;
}
.panel.animate-in-left {
animation: slideInFromLeft 0.4s forwards ease-out;
}
.panel.animate-out-left {
animation: slideOutToLeft 0.3s forwards ease-in;
}
.panel.animate-in-top {
animation: slideInFromTop 0.4s forwards ease-out;
}
.panel.animate-out-top {
animation: slideOutToTop 0.3s forwards ease-in;
}
.panel.animate-in-bottom {
animation: slideInFromBottom 0.4s forwards ease-out;
}
.panel.animate-out-bottom {
animation: slideOutToBottom 0.3s forwards ease-in;
}
5. The script orchestrates everything. It listens for clicks on the trigger buttons and then applies or removes CSS classes to show and hide the panels.
document.addEventListener('DOMContentLoaded', () => {
// Get references to the overlay element
const overlay = document.getElementById('overlay');
// Variables to keep track of the currently open panel and the element that had focus before opening the panel
let openPanel = null;
let lastFocusedElement = null;
/**
* Opens a given panel.
* @param {HTMLElement} panel - The panel element to open.
*/
const open = (panel) => {
// If no panel is provided or a panel is already open, do nothing
if (!panel || openPanel) return;
// Get the direction for the animation from the panel's data attribute
const direction = panel.dataset.direction;
// Store the currently focused element to restore focus later
lastFocusedElement = document.activeElement;
// Reset panel classes and add 'is-open' and the appropriate 'animate-in' class
panel.className = 'panel'; // Resets all classes except 'panel'
panel.classList.add('is-open', `animate-in-${direction}`);
// Apply specific Tailwind classes based on the panel's direction for positioning and sizing
if (direction === 'right' || direction === 'left') {
// For side panels: full height, responsive width, and padding
panel.classList.add('top-0', 'h-full', 'w-11/12', 'sm:max-w-md', 'p-6', 'sm:p-8');
panel.classList.add(direction === 'right' ? 'right-0' : 'left-0');
} else {
// For top/bottom panels: full width, auto height, and padding
panel.classList.add('left-0', 'w-full', 'h-auto', 'p-6', 'sm:p-8');
panel.classList.add(direction === 'bottom' ? 'bottom-0' : 'top-0');
}
// Show the overlay
overlay.classList.add('is-open');
// Set the currently open panel
openPanel = panel;
// Prevent body scrolling when a panel is open
document.body.style.overflow = 'hidden';
// Focus on the close button for accessibility
panel.querySelector('.close-btn').focus();
};
/**
* Closes the currently open panel.
*/
const close = () => {
// If no panel is open, do nothing
if (!openPanel) return;
// Get the direction for the animation
const direction = openPanel.dataset.direction;
// Remove the 'animate-in' class and add the appropriate 'animate-out' class
openPanel.classList.remove(`animate-in-${direction}`);
openPanel.classList.add(`animate-out-${direction}`);
// Hide the overlay
overlay.classList.remove('is-open');
// Add an event listener to run code after the animation ends
openPanel.addEventListener('animationend', () => {
// Remove the 'is-open' and 'animate-out' classes to fully hide the panel
openPanel.classList.remove('is-open', `animate-out-${direction}`);
// Reset the open panel variable
openPanel = null;
// Restore body scrolling
document.body.style.overflow = '';
}, {
once: true
}); // The event listener will only run once
// Restore focus to the element that was focused before the panel opened
if (lastFocusedElement) lastFocusedElement.focus();
};
// --- Event Listeners ---
// Attach click listeners to all buttons that trigger a panel
document.querySelectorAll('[data-target-panel]').forEach(button => {
button.addEventListener('click', e => {
// Get the target panel's ID from the data attribute
const panelId = e.currentTarget.dataset.targetPanel;
// Find the panel element by its ID
const panel = document.getElementById(panelId);
// Open the found panel
open(panel);
});
});
// Attach click listeners to all close buttons within panels
document.querySelectorAll('.close-btn').forEach(button => {
button.addEventListener('click', close);
});
// Close the panel when the overlay is clicked
overlay.addEventListener('click', close);
// Close the panel when the Escape key is pressed
document.addEventListener('keydown', e => {
if (e.key === 'Escape' && openPanel) {
close();
}
});
});
FAQs
Q: Can I use this without TailwindCSS?
A: Yes. The core logic only adds and removes classes like panel, is-open, and animate-in-right. The TailwindCSS classes in the HTML are just for styling the examples. You can replace them with your own CSS.
Q: How does it handle accessibility?
A: The script includes two important accessibility features. First, it traps focus: when a panel opens, it moves focus to the close button inside. When it closes, it returns focus to the element that originally opened it (lastFocusedElement). Second, it allows users to close the active panel by pressing the Escape key.
Q: Can I change the animation speed or easing?
A: Yes, you can adjust the duration and timing function directly in the CSS animation properties. For example, to make the right panel slide in faster, you would change .panel.animate-in-right { animation: slideInFromRight 0.2s forwards ease-out; }.
Q: Can I have multiple panels open simultaneously?
A: The current implementation allows only one panel open at a time through the openPanel variable check. To support multiple panels, remove the if (openPanel) return; check and modify the focus management logic.
Q: How do I handle panel content that exceeds viewport height?
A: Add overflow-y: auto to the .panel-content element for scrollable content within the panel. The library handles body scroll prevention automatically when panels are open.
The post Multi-Direction Offcanvas Drawers with Smooth Sliding Animations appeared first on CSS Script.
Discover more from RSS Feeds Cloud
Subscribe to get the latest posts sent to your email.
