@components-1812/offcanvas
Version:
A web component for custom offcanvas, lateral panel
613 lines (403 loc) • 19.2 kB
Markdown
# Offcanvas custom element

## Examples
- [**Codepen**](https://codepen.io/collection/MWrrvV):
[`Overview`](https://codepen.io/FrancoJavierGadea/pen/WbQzvqB)
[`2 - Panel placement`](https://codepen.io/FrancoJavierGadea/pen/QwjmbXX)
[`3 - Panel scroll`](https://codepen.io/FrancoJavierGadea/pen/empMNqZ)
[`4 - Custom icons`](https://codepen.io/FrancoJavierGadea/pen/xbwWGvd)
[`5 - Backdrop content`](https://codepen.io/FrancoJavierGadea/pen/GgpxJVX)
[`6 - Backdrop static`](https://codepen.io/FrancoJavierGadea/pen/NPGYqQV)
[`7 - Panel responsive`](https://codepen.io/FrancoJavierGadea/pen/yyYKYBN)
- [**Stackblitz**]():
[`Overview`](https://stackblitz.com/edit/vitejs-vite-puxrtegx?file=index.html)
<br>
## Installation
#### NPM
```bash
npm install @components-1812/offcanvas
```
- [`JSON visualizer package`](https://www.npmjs.com/package/@components-1812/offcanvas)
#### CDN
Load the component bundle directly from a CDN — this will automatically register the element and inject styles:
```html
<!-- Classic script -->
<script src="https://cdn.jsdelivr.net/npm/@components-1812/offcanvas@0.0.3/dist/index.min.js"></script>
<!-- ES module -->
<script type="module">
import "https://cdn.jsdelivr.net/npm/@components-1812/offcanvas@0.0.3/dist/index.min.js";
</script>
```
Alternatively, you can manually import, load the styles, and define the element yourself:
```html
<script type="module">
import Offcanvas from "https://cdn.jsdelivr.net/npm/@components-1812/offcanvas@0.0.3/src/Offcanvas.min.js";
// Load the stylesheet from a CDN
Offcanvas.stylesSheets.links.push(
"https://cdn.jsdelivr.net/npm/@components-1812/offcanvas@0.0.3/src/Offcanvas.min.css"
);
//Define with the default tag
Offcanvas.define();
</script>
```
- **jsdelivr**: [`Offcanvas package`](https://www.jsdelivr.com/package/npm/@components-1812/offcanvas)
[`Offcanvas.js`](https://cdn.jsdelivr.net/npm/@components-1812/offcanvas@0.0.3/src/Offcanvas.min.js)
[`Offcanvas.css`](https://cdn.jsdelivr.net/npm/@components-1812/offcanvas@0.0.3/src/Offcanvas.css)
[`Offcanvas.min.js`](https://cdn.jsdelivr.net/npm/@components-1812/offcanvas@0.0.3/dist/Offcanvas.min.js)
[`Offcanvas.min.css`](https://cdn.jsdelivr.net/npm/@components-1812/offcanvas@0.0.3/dist/Offcanvas.min.css)
[`Bundle`](https://cdn.jsdelivr.net/npm/@components-1812/offcanvas@0.0.3/dist/index.min.js)
- **unpkg**: [`Offcanvas package`](https://app.unpkg.com/@components-1812/offcanvas)
[`Offcanvas.js`](https://unpkg.com/@components-1812/offcanvas@0.0.3/src/Offcanvas.js)
[`Offcanvas.css`](https://unpkg.com/@components-1812/offcanvas@0.0.3/src/Offcanvas.css)
[`Offcanvas.min.js`](https://unpkg.com/@components-1812/offcanvas@0.0.3/dist/Offcanvas.min.js)
[`Offcanvas.min.css`](https://unpkg.com/@components-1812/offcanvas@0.0.3/dist/Offcanvas.min.css)
[`Bundle`](https://unpkg.com/@components-1812/offcanvas@0.0.3/dist/index.min.js)
<br>
## Usage
### Vite Ecosystem
If you are using **Vite** or a **Vite-based** framework such as **Astro**, you can import the component in a *client-side script*:
```js
import '@components-1812/offcanvas';
```
and use it in your HTML:
```html
<custom-offcanvas open variant="right global" handle-button>
<!-- Panel content -->
<div slot="header">Header</div>
<div>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quod.</p>
</div>
<div slot="footer">Footer</div>
<!--Custom icons -->
<div slot="close-button">❌</div>
<div slot="handle-button" data-rotate-icon>➡️</div>
<!-- Backdrop content -->
<div slot="backdrop" style="position: absolute; top: 0; left: 0;">
<p>This is the backdrop content.</p>
<p>You can add any content here.</p>
</div>
</custom-offcanvas>
```
### Other Frameworks
If you are using a builder or framework that does not support importing with `?raw`,
you can load and register the component using the `bundle` version in `dist/index.min.js`, which includes all CSS injected via **CSS-in-JS** and `AdoptedStyleSheets`:
```js
import '@components-1812/offcanvas/dist/index.min.js';
```
For customizing the component definition or manually loading the stylesheets, see [Defining and
Adding Stylesheets Manually](#Defining and Adding Stylesheets Manually).
> **Node**
>
> The `dist` folder includes minified versions: `Offcanvas.min.css` and `Offcanvas.min.js`, which can be used anywhere.
<br>
## Defining and Adding Stylesheets Manually
If you want to add custom stylesheets to the component or need to load stylesheets from a different path, you can do it like this:
- ### AdoptedStyleSheets (recommended)
Using your builder’s import raw method, `CSSStyleSheet`, and the component’s `AdoptedStyleSheets` property:
```js
import Offcanvas from "@components-1812/offcanvas/Offcanvas.js";
import OffcanvasRawCSS from "@components-1812/offcanvas/Offcanvas.css?raw";
//Create a CSSStyleSheet and add it to the component
const OffcanvasCSS = new CSSStyleSheet();
OffcanvasCSS.replaceSync(OffcanvasRawCSS);
Offcanvas.stylesSheets.adopted.push(OffcanvasCSS);
//Define the component with default tag name
Offcanvas.define();
```
- ### Raw CSS in a `<style>` tag
Using a `<style>` tag inside the shadow root of the component:
```js
import Offcanvas from "@components-1812/offcanvas/Offcanvas.js";
//Add the raw stylesheet to the component
const OffcanvasRawCSS = `:host {
/* ...Offcanvas.css styles... */
}`;
Offcanvas.stylesSheets.raw.push(OffcanvasRawCSS);
//Define the component custom tag name
Offcanvas.define('other-custom-tag-name');
```
- ### External CSS files in a `<link>` tag
Using a `<link>` tag inside the shadow root of the component:
```js
import Offcanvas from "@components-1812/offcanvas/Offcanvas.js";
//Add the url source stylesheets to the component
Offcanvas.stylesSheets.links.push('https://cdn.example.com/Offcanvas.css');
//Define the component manually
customElements.define('custom-offcanvas', Offcanvas);
```
<br>
## Customization: CSS Variables
```css
--offcanvas-position: absolute;
--offcanvas-z-index: 8010;
/* Panel */
--offcanvas-panel-width: 300px;
--offcanvas-panel-height: 100%;
--offcanvas-panel-padding: 5px;
--offcanvas-panel-transition: margin 0.3s ease-in-out;
--offcanvas-panel-header-padding: 5px;
--offcanvas-panel-body-padding: 5px;
--offcanvas-panel-footer-padding: 5px;
--offcanvas-shadow: 0 1px 3px 0 #3c40434d, 0 4px 8px 3px #3c404326;
--offcanvas-panel-bg: #222;
--offcanvas-panel-color: #fff;
--offcanvas-panel-header-bg: var(--offcanvas-panel-bg);
--offcanvas-panel-header-color: var(--offcanvas-panel-color);
--offcanvas-panel-footer-bg: var(--offcanvas-panel-bg);
--offcanvas-panel-footer-color: var(--offcanvas-panel-color);
--offcanvas-panel-border-width: 1px;
--offcanvas-panel-border-style: solid;
--offcanvas-panel-border-color: #ccc;
--offcanvas-panel-border: var(--offcanvas-panel-border-width) var(--offcanvas-panel-border-style) var(--offcanvas-panel-border-color);
--offcanvas-panel-border-radius: 0px;
/* Backdrop */
--offcanvas-backdrop-bg: #00000080;
--offcanvas-backdrop-color: #fff;
--offcanvas-backdrop-transition: background-color 0.3s ease-in-out;
/* Close button */
--offcanvas-close-button-width: 40px;
--offcanvas-close-button-height: 40px;
--offcanvas-close-button-padding: 10px;
--offcanvas-close-button-bg: transparent;
--offcanvas-close-button-color: #fff;
--offcanvas-close-button-border: none;
--offcanvas-close-button-font-size: 1.5rem;
--offcanvas-close-button-cursor: pointer;
/* Handle button */
--offcanvas-handle-button-width: 50px;
--offcanvas-handle-button-height: 100px;
--offcanvas-handle-button-padding: 5px;
--offcanvas-handle-button-bg: #444;
--offcanvas-handle-button-color: #fff;
--offcanvas-handle-button-border: none;
--offcanvas-handle-button-border-radius: 10px;
--offcanvas-handle-button-cursor: pointer;
--offcanvas-handle-button-font-size: 1rem;
--offcanvas-handle-button-shadow: var(--offcanvas-shadow);
```
#### Top and Bottom Offcanvas
```css
--offcanvas-panel-width: 100%;
--offcanvas-panel-height: 300px;
--offcanvas-handle-button-width: 100px;
--offcanvas-handle-button-height: 50px;
--offcanvas-panel-shadow: 1px 0 3px 0 #3c40434d, 4px 0 8px 3px #3c404326;
```
### Responsive Panel
For responsive design, there will be times when you want the panel to occupy the **full width of the screen**, especially on mobile devices.
You can achieve this by adding a `CSS media query` like the following:
```css
custom-offcanvas {
@media (width <= 500px) {
--offcanvas-panel-width: 100%;
}
}
```
This will ensure that the offcanvas panel expands to the **full screen width** on devices with a width of `500px` or less.
> See the example: [7 - Panel responsive](https://codepen.io/FrancoJavierGadea/pen/yyYKYBN)
<br><br>
## Open and close
You can open the **offcanvas panel** using the `open` attribute:
```html
<custom-offcanvas open></custom-offcanvas>
```
Via JavaScript, you can use the `open` property (mirrored attribute):
```js
const offcanvas = document.querySelector('custom-offcanvas');
// Open the panel
offcanvas.open = true;
// Close the panel
offcanvas.open = false;
```
You can also control the panel with the methods: `.show()`, `.hide()`, `.toggle()`
```js
const offcanvas = document.querySelector('custom-offcanvas');
// Show the panel
offcanvas.show();
// Hide the panel
offcanvas.hide();
// Toggle the panel (switch state)
offcanvas.toggle(); // toggle current state
offcanvas.toggle(true); // force open
offcanvas.toggle(false); // force close
```
<br><br>
## Slots
The **default slot** is reserved for the **panel body**, which contains the main content.
You can also use the following named slots:
- `slot="header"` – for the panel header.
- `slot="footer"` – for the panel footer.
```html
<custom-offcanvas open variant="right" handle-button>
<div slot="header">Header</div>
<div>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quod.</p>
</div>
<div slot="footer">Footer</div>
</custom-offcanvas>
```
#### Customizing Buttons
You can replace the default icons for the **close button** and the **handle button** (if present) using:
- `slot="close-button"`
- `slot="handle-button"`
para modificar los iconos por defecto del close button y el handle button (si esta) puedes usar `slot="close-button"` y `slot="handle-button"`
```html
<custom-offcanvas open variant="right" handle-button>
<div slot="close-button">❌</div>
<div slot="handle-button" data-rotate-icon>➡️</div>
</custom-offcanvas>
```
> **Note**
>
> For the **handle button**, you can add the attribute `data-rotate-icon` so that the icon always rotates to point in the correct direction according to the panel position and open/closed state.
>
> To work correctly, the icon must initially point to the **right**.
#### Backdrop
The `backdrop` slot allows you to add custom content positioned around the panel for richer interactions or decorations.
Elements in this slot should use `position: absolute;`
Since the `backdrop` changes size when the **panel** opens or closes, the elements will move together with the panel.
```html
<custom-offcanvas open variant="left" handle-button>
<!-- Backdrop content -->
<div slot="backdrop" style="position: absolute; top: 0; left: 0;">
<p>This is the backdrop content.</p>
<p>You can add any content here.</p>
</div>
<div slot="backdrop" style="position: absolute; bottom: 0; right: 0;">
<p>This is another backdrop content.</p>
<p>You can customize the backdrop as needed.</p>
</div>
</custom-offcanvas>
```
You can also control the visibility of backdrop elements based on the panel state using attributes like:
- `data-hide-when-closed` – hides the element when the panel is closed.
- `data-hide-when-opened` – hides the element when the panel is open.
```html
<custom-offcanvas open variant="left" handle-button>
<!-- Backdrop content -->
<div slot="backdrop" style="position: absolute; top: 0; right: 0;" data-hide-when-closed>
<p>This content will hide when the offcanvas is closed.</p>
</div>
<div slot="backdrop" style="position: absolute; bottom: 0; left: 0;" data-hide-when-opened>
<p>This content will hide when the offcanvas is opened.</p>
</div>
</custom-offcanvas>
```
> See the example: [5 - Backdrop content](https://codepen.io/FrancoJavierGadea/pen/GgpxJVX)
<br><br>
## Variants
### Panel Global and Local Positioning
You can control whether the **offcanvas panel** is positioned relative to the `viewport` or inside its container using the `variant` attribute:
- `local` (default) – The panel is positioned `absolute` relative to its nearest positioned ancestor.
- `global` – The panel is positioned `fixed` relative to the viewport. This is useful for modals or overlays.
```html
<!-- Fixed to the viewport -->
<custom-offcanvas variant="top global"></custom-offcanvas>
<!-- Positioned inside a container -->
<div class="container" style="position: relative;">
<custom-offcanvas variant="left local"></custom-offcanvas>
</div>
```
### Panel Placement
The `variant` attribute defines the position of the **offcanvas panel**: `left`, `right`, `top`, `bottom`
```html
<custom-offcanvas variant="left"></custom-offcanvas>
<custom-offcanvas variant="right"></custom-offcanvas>
<custom-offcanvas variant="top"></custom-offcanvas>
<custom-offcanvas variant="bottom"></custom-offcanvas>
```
> See the example: [2 - Panel placement](https://codepen.io/FrancoJavierGadea/pen/QwjmbXX)
### Panel Scroll
By default, the panel does not scroll. You can customize the scroll behavior using the following variants:
- `scroll-full`: The entire panel content scrolls.
- `scroll-inner`: All content scrolls except the header
- `scroll-body`: Only the body scrolls; header and footer remain fixed.
```html
<custom-offcanvas variant="scroll-full"></custom-offcanvas>
<custom-offcanvas variant="scroll-inner"></custom-offcanvas>
<custom-offcanvas variant="scroll-body"></custom-offcanvas>
```
> See the example: [3 - Panel scroll](https://codepen.io/FrancoJavierGadea/pen/empMNqZ)
### Handle button
By default, the component does not provide any control to open the panel; you must add it manually.
With the handle-button attribute, the component automatically adds a button inside the backdrop content, centered along the edge of the panel. This button allows users to open and close the panel.
```html
<custom-offcanvas variant="right" handle-button></custom-offcanvas>
```
### Static and Transparent Backdrop
- `backdrop-static`: By default, clicking on the backdrop closes the **panel**.
You can disable this behavior by adding the backdrop-static variant:
```html
<custom-offcanvas variant="backdrop-static"></custom-offcanvas>
```
With this `variant`, clicking on the backdrop won’t close the **panel**, and the page behind it remains interactive while the **panel** is open.
- `backdrop-transparent`
If you want to hide the backdrop background when the **panel** is open, you have two options:
- Override the CSS variable: `--offcanvas-backdrop-bg`
- Or, more easily, use the `backdrop-transparent` variant:
```html
<custom-offcanvas variant="backdrop-transparent"></custom-offcanvas>
```
<br><br>
## API
### Attributes
- `variant`: Panel position or visual style.
- Panel position: `left`, `top`, `right`, `bottom`
- Panel scroll: `scroll-full`, `scroll-inner`, `scroll-body`
- Page position: `global`, `local`
- `open` (boolean, default false): Whether the panel is open.
- `handle-button` (boolean, default false): Whether to show a floating handle button.
### Properties
#### Mirrored
- `variant` (string): Panel position or visual style.
- `open` (boolean): Whether the panel is open.
- `handleButton` (boolean): Whether the handle button is visible.
### Methods
`show()`: Opens the panel (open = true).
`hide()`: Closes the panel (open = false).
`toggle(force?: boolean)`: Toggles the panel. If force is `true` or `false`, sets the panel state accordingly.
### Events
- `ready-links`: Fired when all external stylesheet links have finished loading. Provides a detail with the results of each stylesheet.
- `ready`: Dispatched when the component has finished initializing (end of connectedCallback)
### Static properties
- `VERSION` (string): Component version (0.0.3).
- `DEFAULT_TAG_NAME` (string): Default tag name (`custom-offcanvas`) use it to define the custom element in `index.js` and `define.js`
- `DEFAULT_ICONS` (object): Default svg icons for `close-button` and `handle-button`.
```js
Offcanvas.DEFAULT_ICONS = {
'close-button': `<svg>...</svg>`,
'handle-button': `<svg data-rotate-icon>...</svg>`
}
```
- `stylesSheets` (object): Contains `links`, `adopted`, and `raw` stylesheets to apply to the component.
```js
Offcanvas.stylesSheets = {
links: [],//string url css source
adopted: [],//CSSStyleSheet instances
raw: [],//string raw css
};
```
- `define(tagName?, stylesSheets?)` (function): Defines the custom element and optionally adds stylesheets if the element is not already registered.
```js
Offcanvas.define('custom-offcanvas', {
links: ['https://cdn.example.com/Offcanvas.css'],
adopted: [new CSSStyleSheet()],
raw: [':host { /* ...Offcanvas.css styles... */ }'],
});
```
> Internally, this method calls `window.customElements.define(tagName, Offcanvas)`
### Slots
- `default`: Main content of the panel
- `header`: Header content of the panel
- `footer`: Footer content of the panel
- `close-button`: Icon or content for the close button in the header of the panel
- `handle-button`: Icon or content for the handle button if added
- `backdrop`: Content inside the backdrop (around the panel)
<br><br>
## License
This package is distributed under the [MIT license](./LICENSE).
## Credits
Default icons used in this package are sourced from the [Bootstrap Icons](https://icons.getbootstrap.com/) project, licensed under the MIT license.
© 2019–2024 The Bootstrap Authors
- [x-lg](https://icons.getbootstrap.com/icons/x-lg/)
- [arrow-bar-right](https://icons.getbootstrap.com/icons/arrow-bar-right/)