Skip to content

Instantly share code, notes, and snippets.

@nfreear
Last active May 1, 2019 20:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nfreear/1f1b725d67c9fc9ae3e2d30332978cd0 to your computer and use it in GitHub Desktop.
Save nfreear/1f1b725d67c9fc9ae3e2d30332978cd0 to your computer and use it in GitHub Desktop.
Responsive snake layout — with Vue & Bootstrap | © The Open University (IET), Nick Freear, Richard Greenwood, 26-April-2019 | https://codepen.io/nfreear/pen/QPYRVZ
<!doctype html> <title> *Snake layout — Vue & Bootstrap </title>
<link rel=stylesheet href="https://unpkg.com/bootstrap@4.3.1/dist/css/bootstrap.min.css" />
<style>
:root {
--oj-card-width: 416px;
--oj-card-height: 234px;
}
.wide.container { max-width: 1775px; /* Was: 1920px */ width: 100%; }
@media screen and (min-width: 1341px) and (max-width: 1774px) {
.wide.container { max-width: 1328px; } /* 3-across */
}
@media screen and (min-width: 909px) and (max-width: 1340px) {
.wide.container { max-width: 895px; } /* 2-across */
}
@media screen and (max-width: 908px) {
.wide.container { max-width: 438px; padding: 0; } /* 1-accross */
}
.cursor-help { cursor: help; border-bottom: 2px dotted darkorange; }
.snake-layout > * > div { background: darkgreen; color: white; font-size: 3rem; height: var(--oj-card-height); width: var(--oj-card-width); }
.XX--d-flex > * { transition: all 5s ease-in-out; }
</style>
<div id="app">
<div class="container">
<h1> Snake layout — with Vue & Bootstrap </h1>
<h2><i class="cursor-help" title="CSS zoom is non-standard; no support on Firefox"
> {{ zoomStyle }} </i></h2>
</div>
<div class="custom wide container" v-bind:data-X-style="zoomStyle">
<snake-layout v-bind:items="items"></snake-layout>
</div>
</div>
<template id="snake-layout-template">
<ol v-if="dataItems" class="snake-layout d-flex flex-wrap justify-content-between list-unstyled">
<li v-for="item in dataItems" v-bind:title="'Item num ' + (item.id + 1)" v-bind:class="itemClass">
<div class="d-flex justify-content-center align-items-center"><slot name="body">{{ item.name + 1 }}</slot></div>
</li>
</ol>
<h2 v-else class="alert alert-danger">Error. No items ...?!</h2>
</template>
<script id="onResizeThrottled.js">
// Throttling :~ https://developer.mozilla.org/en-US/docs/Web/API/Document/defaultView/resize_event#Examples
const onResizeThrottled = (listenerFunc, delay) => {
delay = parseInt(delay) || 60; // Was: 100 // Milliseconds.
let resizeTaskId = null;
window.addEventListener('resize', evt => {
if (resizeTaskId !== null) {
window.clearTimeout(resizeTaskId);
}
resizeTaskId = window.setTimeout(() => {
resizeTaskId = null;
listenerFunc(evt);
}, delay);
});
window.dispatchEvent(new Event('resize'));
};
</script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script id="SnakeLayout.vue">
// const MAX_COUNT = 59;
Vue.component('SnakeLayout', {
template: '#snake-layout-template',
props: { // Public!
items: {
type: Array,
required: true,
// validator: value => value.length <= MAX_COUNT, // Not applicable!
},
},
data () { // Private!
return {
MAX_PER_ROW: 4,
CARD_WIDTH: 416, // Pixels.
MARGIN: 0, // 5, // Pixels.
itemClass: 'p-2',
dataItems: null, // Array,
ELEMS: [],
}
},
methods: {
onResize (event) {
const perRow = this.countCardsPerRow()
console.warn('Resize:', event, this.$el.offsetWidth / this.CARD_WIDTH, perRow)
this.removeBootstrapFlexClasses();
if (perRow > 1) {
this.ELEMS.forEach((el, idx) => {
if (this.isOnBend(idx, perRow)) {
el.classList.add('justify-content-' + (this.isEnd(idx, perRow) ? 'end' : 'start'))
el.classList.add('w-100')
el.classList.add('d-flex')
}
el.style.order = this.flexOrderOrReverse(idx, perRow)
})
}
},
// ----------------------- PRIVATE -----------------------
// Reset: remove Bootstrap Flexbox styles (must use 'classList')
removeBootstrapFlexClasses () {
this.ELEMS.forEach(el => {
// el.classList.remove('p-2')
el.classList.remove('w-100')
el.classList.remove('justify-content-start')
el.classList.remove('justify-content-end')
el.classList.remove('d-flex')
});
},
countCardsPerRow () {
const PER_ROW = parseInt( (this.$el.offsetWidth - this.MARGIN) / this.CARD_WIDTH ) // Margins ?
if (PER_ROW > this.MAX_PER_ROW) {
throw new Error('Maximum number of cards per row exceeded:' + PER_ROW);
}
return PER_ROW;
},
isOnBend (idx, perRow) {
return (idx + 1) % (perRow + 1) === 0;
},
isEnd (idx, perRow) {
return (idx + 1) % this.getWavelength(perRow) !== 0;
},
getWavelength (perRow) {
return 2 * (perRow + 1)
},
flexOrderOrReverse (idx, perRow) {
let orderResult = idx + 1; // Default.
const RHYTHM = this.getWavelength( perRow ) // this.RHYTHM_LOOKUP[ perRow ];
const TIMES = parseInt(idx / RHYTHM) + 1; // >= 1;
const OFFSET = (idx + 1) % RHYTHM - perRow; // -3 ... 4;
if (OFFSET > 1) {
orderResult = (TIMES * RHYTHM) - OFFSET;
}
// console.warn('Dir:', idx, OFFSET, dir, orderResult)
return orderResult;
},
},
mounted () {
this.dataItems = this.items;
window.setTimeout(() => {
this.ELEMS = Array.from(this.$el.querySelectorAll(':scope > *')) // Direct children.
onResizeThrottled(this.onResize)
console.warn('SnakeLayout.mounted:', this, this.$el)
}, 10);
}
})
</script>
<script id="App.js">
new Vue({
el: '#app',
data () { // Private!
return {
DEFAULT_ZOOM: 100,
DEFAULT_COUNT: 30,
}
},
computed: {
items () {
let theItems = [];
const LENGTH = this.paramInt(/[&?]count=([1-9]\d+)[&]?/, this.DEFAULT_COUNT)
// https://stackoverflow.com/questions/3746725/how-to-create-an-array-containing-1-n/54996234
const MAKE_ARRAY = Array.from(Array(LENGTH).keys())
MAKE_ARRAY.forEach((name, id) => theItems.push({ id, name }))
console.warn('App.theItems:', theItems);
return theItems.reverse()
},
zoomStyle () {
const ZOOM = this.paramInt(/[&?]zoom=([4-9]0)[&]?/, this.DEFAULT_ZOOM)
// console.warn('App.zoom:', ZOOM, '%')
return 'zoom: ZOOM%'.replace(/ZOOM/, ZOOM)
},
},
methods: {
paramInt (regex, defaultVal) {
const MATCHES = window.location.href.match(regex);
return MATCHES ? parseInt(MATCHES[ 1 ]) : defaultVal;
}
},
})
</script>
<pre class="container">
© The Open University (IET), Nick Freear, Richard Greenwood, 26-April-2019 | https://codepen.io/nfreear/pen/QPYRVZ | https://gist.github.com/nfreear/1f1b725d67c9fc9ae3e2d30332978cd0
* (https://gist.github.com/nfreear/4b786f19551759a8c8dd1ac8c22a1fac)
* https://css-tricks.com/snippets/css/a-guide-to-flexbox/
* https://codepen.io/team/css-tricks/pen/EKEYob
* https://getbootstrap.com/docs/4.0/utilities/flex/
</pre>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment