Last active
March 1, 2019 16:11
-
-
Save DaveBitter/08fee41f23e82d0385bf954917b7da23 to your computer and use it in GitHub Desktop.
Animated grid modal
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"items": [ | |
{}, | |
{}, | |
{}, | |
{}, | |
{}, | |
{}, | |
{}, | |
{}, | |
{}, | |
{}, | |
{}, | |
{} | |
] | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div class="grid-modal" data-module="examples/grid-modal/GridModal"> | |
<div class="grid-modal__grid" data-grid> | |
{% for item in items %} | |
<div class="grid-modal__grid-item"> | |
<div class="grid-modal__grid-item-content" data-grid-item> | |
<img class="grid-modal__grid-item-image" src="/static/img/placeholders/ny.jpg"></img> | |
<button class="grid-modal__grid-item-close" data-grid-item-close> | |
<svg class="svg svg--close" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"> | |
<path d="M8 5.9130435L2.086957 0 0 2.0869565 5.913043 8 0 13.9130435 2.086957 16 8 10.0869565 13.913043 16 16 13.9130435 10.086957 8 16 2.0869565 13.913043 0z" /> | |
</svg> | |
</button> | |
</div> | |
</div> | |
{% endfor %} | |
</div> | |
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.grid-modal { | |
&__grid { | |
position: relative; | |
display: flex; | |
flex-direction: column; | |
@include mq($mq-mob) { | |
flex-direction: row; | |
flex-wrap: wrap; | |
} | |
&-item { | |
height: 200px; | |
margin: $spacing-sml; | |
@include mq($mq-mob) { | |
width: calc(50% - (2 * #{$spacing-sml})); | |
} | |
@include mq($mq-tab--sml) { | |
width: calc((100%/3) - (2 * #{$spacing-sml})); | |
} | |
&-content { | |
position: relative; | |
width: 100%; | |
height: 100%; | |
&[data-is-modal], | |
&[data-transitioning] { | |
z-index: 100; | |
} | |
&[data-transitioning] { | |
transition: | |
max-width $transition-timing-med $transition-easing-cubic, | |
max-height $transition-timing-med $transition-easing-cubic, | |
transform $transition-timing-med $transition-easing-cubic; | |
} | |
&[data-is-modal] { | |
position: fixed; | |
top: 0; | |
right: 0; | |
bottom: 0; | |
left: 0; | |
} | |
} | |
&-image { | |
width: 100%; | |
height: 100%; | |
object-fit: cover; | |
pointer-events: none; | |
} | |
&-close { | |
z-index: 1; | |
display: none; | |
position: absolute; | |
top: $spacing-med; | |
right: $spacing-med; | |
width: 50px; | |
height: 50px; | |
background-color: transparent; | |
border: none; | |
cursor: pointer; | |
[data-is-modal] & { | |
display: block; | |
} | |
svg { | |
width: 16px; | |
height: 16px; | |
fill: white; | |
pointer-events: none; | |
} | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Helpers | |
const setAsyncTimeout = (cb, timeout = 0) => | |
new Promise(resolve => setTimeout(() => { | |
cb(); | |
resolve(); | |
}, timeout)); | |
// Constants | |
const constants = { | |
attributes: { | |
DATA_TRANSITIONING: 'data-transitioning', | |
DATA_IS_MODAL: 'data-is-modal', | |
DATA_GRID: 'data-grid', | |
DATA_GRID_ITEM: 'data-grid-item', | |
DATA_GRID_ITEM_CLOSE: 'data-grid-item-close' | |
}, | |
directions: { | |
GROW: 'grow', | |
SHRINK: 'shrink' | |
} | |
}; | |
class GridModal { | |
constructor(element, options) { | |
this._element = element; | |
this._options = options; | |
this._init(); | |
} | |
_state = { | |
activeItem: null | |
} | |
/** | |
* Set bounds for node | |
* @param {Node} node - Node to set bounds for | |
*/ | |
_setBounds(node, bounds) { | |
node.style = ` | |
position: fixed; | |
top: 0; | |
left: 0; | |
transform: translate3d(${bounds.x}px, ${bounds.y}px, 0); | |
max-height: ${bounds.height}px; | |
max-width: ${bounds.width}px; | |
`; | |
} | |
/** | |
* Animate node based on bounds and direction | |
* @param {Node} node - Node to animate | |
* @param {Node} from - Starting bounds | |
* @param {Node} to - Ending bounds | |
* @param {Node} direction - Direction of animation (grow/shrink) | |
*/ | |
async _animate(node, from, to, direction) { | |
// Set start bounds | |
this._setBounds(node, from); | |
// Add transition | |
await setAsyncTimeout(() => { node.setAttribute(constants.attributes.DATA_TRANSITIONING, ''); }, 0); | |
// Set end bounds | |
await setAsyncTimeout(() => { this._setBounds(node, to); }, 0); | |
// Clean up animation | |
await setAsyncTimeout(() => { | |
node.removeAttribute(constants.attributes.DATA_TRANSITIONING); | |
node[direction === constants.directions.GROW ? 'setAttribute' : 'removeAttribute'](constants.attributes.DATA_IS_MODAL, ''); | |
node.style = null; | |
}, 250); | |
} | |
/** | |
* Get bounds for shrink animation and call animate | |
*/ | |
_shrink() { | |
const from = this._state.activeItem.getBoundingClientRect(); | |
const to = this._state.activeItem.parentNode.getBoundingClientRect(); | |
this._animate(this._state.activeItem, from, to, constants.directions.SHRINK); | |
} | |
/** | |
* Get bounds for grow animation and call animate | |
*/ | |
_grow() { | |
const from = this._state.activeItem.getBoundingClientRect(); | |
const to = { | |
x: 0, | |
y: 0, | |
width: window.innerWidth, | |
height: window.innerHeight | |
} | |
this._animate(this._state.activeItem, from, to, constants.directions.GROW); | |
} | |
/** | |
* Handle click for grid item | |
* @param {Node} target - Target of clicked node | |
*/ | |
_handleClick({ target }) { | |
if (target.hasAttribute(constants.attributes.DATA_GRID_ITEM)) { | |
this._state.activeItem = target; | |
this._grow(); | |
} | |
if (target.hasAttribute(constants.attributes.DATA_GRID_ITEM_CLOSE)) { this._shrink(); } | |
} | |
/** | |
* Inititalize all event listeners | |
*/ | |
_addEventListeners() { | |
this._grid.addEventListener('click', this._handleClick.bind(this)); | |
} | |
/** | |
* Cache all selectors for further use | |
*/ | |
_cacheSelectors() { | |
this._grid = this._element.querySelector(`[${constants.attributes.DATA_GRID}]`); | |
} | |
/** | |
* Inititalize component | |
*/ | |
_init() { | |
this._cacheSelectors(); | |
this._addEventListeners(); | |
} | |
} | |
export default GridModal; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment