Author: | ivteplo |
---|---|
Views Total: | 1,649 views |
Official Page: | Go to website |
Last Update: | May 18, 2022 |
License: | MIT |
Preview:

Description:
Bottom sheets are used on mobile applications all the time. They offer a simple way to display extra information or actions when needed.
In this article, I will show you how to create a draggable, touch-enabled, iOS & Android inspired bottom sheet with smooth slide animations on the page by using plain JavaScript. Let’s get started.
How to use it:
1. The required HTML structure for the bottom sheet. Feel free to add or remove content by following the comments as follows:
<!-- The sheet component --> <div id="sheet" class="column items-center justify-end" aria-hidden="true"> <!-- Dark background for the sheet --> <div class="overlay"></div> <!-- The sheet itself --> <div class="contents column"> <!-- Sheet controls --> <header class="controls"> <!-- The thing to drag if you want to resize the sheet --> <div class="draggable-area"> <div class="draggable-thumb"></div> </div> <!-- Button to close the sheet --> <button class="close-sheet" type="button" title="Close the sheet">×</button> </header> <!-- Body of the sheet --> <main class="body fill column"> <h2>Hello, World!</h2> </main> </div> </div>
2. The core CSS styles for the bottom sheet. Feel free to override the CSS variables to create your own styles.
:root { --background: #fff; --foreground: #000; --divider: #dcdcdc; --overlay: #888; } @media (prefers-color-scheme: dark) { :root { --background: #000; --foreground: #fff; --divider: #333; } } #sheet { position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 2; visibility: visible; transition: opacity 0.5s, visibility 0.5s; } #sheet[aria-hidden="true"] { opacity: 0; visibility: hidden; pointer-events: none; } #sheet .overlay { position: absolute; top: 0; left: 0; right: 0; bottom: 0; z-index: -1; background: var(--overlay); opacity: 0.5; } #sheet .contents { border-radius: 1rem 1rem 0 0; background: var(--background); position: relative; overflow-y: hidden; --default-transitions: transform 0.5s, border-radius 0.5s; transition: var(--default-transitions); transform: translateY(0); max-height: 100vh; height: 30vh; max-width: 70rem; box-sizing: border-box; padding: 1rem; padding-top: 3rem; } #sheet .contents:not(.not-selectable) { transition: var(--default-transitions), height 0.5s; } #sheet .contents.fullscreen { border-radius: 0; } #sheet[aria-hidden="true"] .contents { transform: translateY(100%); } #sheet .draggable-area { position: absolute; top: 0; left: 0; right: 0; width: 3rem; margin: auto; padding: 1rem; cursor: grab; } #sheet .draggable-thumb { width: inherit; height: 0.25rem; background: var(--divider); border-radius: 0.125rem; } #sheet .close-sheet { position: absolute; right: 0; top: 0; border: none; } #sheet .body { height: 100%; overflow-y: auto; gap: 1rem; }
3. The main JavaScript to enable the bottom sheet.
const $ = document.querySelector.bind(document) const sheet = $("#sheet") const sheetContents = sheet.querySelector(".contents") const draggableArea = sheet.querySelector(".draggable-area") let sheetHeight // in vh const setSheetHeight = (value) => { sheetHeight = Math.max(0, Math.min(100, value)) sheetContents.style.height = `${sheetHeight}vh` if (sheetHeight === 100) { sheetContents.classList.add("fullscreen") } else { sheetContents.classList.remove("fullscreen") } } const setIsSheetShown = (value) => { sheet.setAttribute("aria-hidden", String(!value)) } // Open the sheet when clicking the 'open sheet' button setSheetHeight(Math.min(50, 720 / window.innerHeight * 100)) setIsSheetShown(true) // Hide the sheet when clicking the 'close' button sheet.querySelector(".close-sheet").addEventListener("click", () => { setIsSheetShown(false) }) // Hide the sheet when clicking the background sheet.querySelector(".overlay").addEventListener("click", () => { setIsSheetShown(false) }) const touchPosition = (event) => event.touches ? event.touches[0] : event let dragPosition const onDragStart = (event) => { dragPosition = touchPosition(event).pageY sheetContents.classList.add("not-selectable") draggableArea.style.cursor = document.body.style.cursor = "grabbing" } const onDragMove = (event) => { if (dragPosition === undefined) return const y = touchPosition(event).pageY const deltaY = dragPosition - y const deltaHeight = deltaY / window.innerHeight * 100 setSheetHeight(sheetHeight + deltaHeight) dragPosition = y } const onDragEnd = () => { dragPosition = undefined sheetContents.classList.remove("not-selectable") draggableArea.style.cursor = document.body.style.cursor = "" if (sheetHeight < 25) { setIsSheetShown(false) } else if (sheetHeight > 75) { setSheetHeight(100) } else { setSheetHeight(50) } } draggableArea.addEventListener("mousedown", onDragStart) draggableArea.addEventListener("touchstart", onDragStart) window.addEventListener("mousemove", onDragMove) window.addEventListener("touchmove", onDragMove) window.addEventListener("mouseup", onDragEnd) window.addEventListener("touchend", onDragEnd)