A pure CSS approach that folds offscreen panel flat.
In this article we’ll create an offscreen panel with folds effect and elastic motion content from within.
I saw tons of cool demos over the years on the internet that has been done with Canvas, Threejs and all these heavy techniques . I’ve looking for a simpler way since then!
I had that task the other day to build a chat panel for the project am working on that appears by hitting the “Send a Message” button on the user’s profile, so users can communicate with each other. After getting the main functionality done and fully working, based on our workflow I had to forward to the QA team after getting done so they start testing! But I did not forward for some reasons 🤷♂️. Since I had enough time I wanted to make that chat a foldable animated panel with a less effort 🦸♂️. A few moments later after scratching my beard I decided to use a little bit CSS transition
and clip-path polygon(...)
to accomplish this feature/task.
Demo
I'm using a CRA boilerplate in this demo to create a React component. But you can stick with whatever stack you're comfortable with.
Demo Link: https://elmahdim.github.io/OffScreenPanel/
OffScreenPanel Component
import React, { useState } from 'react';
import classNames from 'classnames';
import PropTypes from 'prop-types';
import styles from './OffScreenPanel.module.css';
export const OffScreenPanel = (props) => {
const [open, toggleVisibility] = useState(false);
const toggle = () => toggleVisibility(!open);
return (
<div className={classNames('offScreenPanel', {
[styles.open]: open
})}>
<button type="button" className={styles.button} onClick={toggle}>
....
</button>
<div className={styles.panel}>
<div className={styles.body}>{open && props.children}</div>
</div>
<div role="presentation" className={styles.overlay} onClick={toggle} />
</div >
);
};
OffScreenPanel.propTypes = {
children: PropTypes.any,
};
export default OffScreenPanel;
The code above represents a functional OffScreenPanel
component that using Hooks and CSS Modules Stylesheet.
Plain HTML
The main elements we need to fold/unfold our panel without React.
<div class="offScreenPanel open">
<div class="panel">
<div class="body">...</div>
</div>
<div role="presentation" class="overlay"></div>
</div>
The class open
is toggleable (via JavaScript) on the offScreenPanel
element. It has no associated styles.
The panel
element is responsable to fold/unfold its layout. We have two options to achieve this effect: add two more extra elements, or use CSS pseudo element!
I'm going to pick the second option which is using pseudo element (::before
, ::after
). It makes our markup cleaner and less HTML codes.
The inner content will be wrapped by the element body
.
Styles
/*
* panel:
* is out of screen (offset right) by default
*/
.panel {
position: fixed;
top: 0;
right: 0;
width: 450px;
bottom: 0;
z-index: 2;
transform: translateX(450px);
}
/*
* panel:
* on open we set its horizontally offset to "0"
*/
.open .panel {
transform: translateX(0);
transition: all 400ms ease;
}
/*
* panel - the folding element [[]]
* make each element half width of its parent (panel) size
*/
.panel::before,
.panel::after {
content: '';
position: absolute;
top: 0;
bottom: 0;
width: 225px;
transition: all 400ms ease-out;
}
/*
* panel - the folding element []]
*/
.panel::before {
clip-path: polygon(100% 10%, 100% 0, 100% 100%, 100% 90%);
left: 0;
}
/*
* panel - the folding element []]
*/
.panel::after {
background: #f0f0f0 linear-gradient(to right, #f7f7f7 0%, #fff 100%);
clip-path: polygon(100% 50%, 100% 0, 100% 100%, 100% 50%);
right: 0;
}
/*
* panel - the folding element []]
*/
.open .panel::before,
.open .panel::after {
clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);
}
/*
* panel - the folding element [[]
* giving the left panel a paper book like background,
* off-white and light grey
*/
.open .panel::before {
transition-delay: 400ms;
background: #f0f0f0 linear-gradient(to right, #f3f3f3 0%, #f1f1f1 48%, #f1f1f1 100%);
}
/*
* body, one thin line centered by default
*/
.body {
position: absolute;
top: 0;
right: 0;
height: 100%;
left: 0;
background-color: #fff;
transition: all 300ms cubic-bezier(0.22, 0.61, 0.36, 1);
z-index: 1;
clip-path: polygon(50% 50%, 50% 50%, 50% 50%, 50% 50%);
}
/*
* body, folded and fits its parent with on open
*/
.open .panel .body {
transition-delay: 0.8s;
clip-path: polygon(0 0, 100% 0, 100% 100%, 0% 100%);
}
/*
* overlay, hidden by default. to overlap the content behind
* and closes the panel on click outside
*/
.overlay {
background-color: rgba(0, 0, 0, 0.2);
position: fixed;
visibility: hidden;
top: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
z-index: 1;
opacity: 0;
}
/*
* overlay, visible on open
*/
.open .panel + .overlay {
opacity: 1;
visibility: visible;
transition: all 400ms ease;
}
Here's how it looks on both default and open states
Top comments (0)