diff options
Diffstat (limited to 'src/site/components/grid/waterfall')
| -rw-r--r-- | src/site/components/grid/waterfall/Waterfall.vue | 129 | ||||
| -rw-r--r-- | src/site/components/grid/waterfall/WaterfallItem.vue | 10 |
2 files changed, 139 insertions, 0 deletions
diff --git a/src/site/components/grid/waterfall/Waterfall.vue b/src/site/components/grid/waterfall/Waterfall.vue new file mode 100644 index 0000000..5a4c569 --- /dev/null +++ b/src/site/components/grid/waterfall/Waterfall.vue @@ -0,0 +1,129 @@ +<template> + <div ref="waterfall" class="waterfall"> + <WaterfallItem + v-for="item in items" + :key="item.id" + :style="{ width: `${itemWidth}px`, marginBottom: `${gutterHeight}px` }" + :width="itemWidth"> + <slot :item="item" /> + </WaterfallItem> + </div> +</template> +<script> +import WaterfallItem from './WaterfallItem.vue'; + +const isBrowser = typeof window !== 'undefined'; +// eslint-disable-next-line global-require +const Masonry = isBrowser ? window.Masonry || require('masonry-layout') : null; +const imagesloaded = isBrowser ? require('imagesloaded') : null; + +export default { + name: 'Waterfall', + components: { + WaterfallItem + }, + props: { + options: { + 'type': Object, + 'default': () => {} + }, + items: { + 'type': Array, + 'default': () => [] + }, + itemWidth: { + 'type': Number, + 'default': 150 + }, + gutterWidth: { + 'type': Number, + 'default': 10 + }, + gutterHeight: { + 'type': Number, + 'default': 4 + } + }, + mounted() { + this.initializeMasonry(); + this.imagesLoaded(); + }, + updated() { + this.performLayout(); + this.imagesLoaded(); + }, + unmounted() { + this.masonry.destroy(); + }, + methods: { + imagesLoaded() { + const node = this.$refs.waterfall; + imagesloaded( + node, + () => { + this.masonry.layout(); + } + ); + }, + performLayout() { + const diff = this.diffDomChildren(); + if (diff.removed.length > 0) { + this.masonry.remove(diff.removed); + this.masonry.reloadItems(); + } + if (diff.appended.length > 0) { + this.masonry.appended(diff.appended); + this.masonry.reloadItems(); + } + if (diff.prepended.length > 0) { + this.masonry.prepended(diff.prepended); + } + if (diff.moved.length > 0) { + this.masonry.reloadItems(); + } + this.masonry.layout(); + }, + diffDomChildren() { + const oldChildren = this.domChildren.filter(element => Boolean(element.parentNode)); + const newChildren = this.getNewDomChildren(); + const removed = oldChildren.filter(oldChild => !newChildren.includes(oldChild)); + const domDiff = newChildren.filter(newChild => !oldChildren.includes(newChild)); + const prepended = domDiff.filter((newChild, index) => newChildren[index] === newChild); + const appended = domDiff.filter(el => !prepended.includes(el)); + let moved = []; + if (removed.length === 0) { + moved = oldChildren.filter((child, index) => index !== newChildren.indexOf(child)); + } + this.domChildren = newChildren; + return { + 'old': oldChildren, + 'new': newChildren, + removed, + appended, + prepended, + moved + }; + }, + initializeMasonry() { + if (!this.masonry) { + this.masonry = new Masonry( + this.$refs.waterfall, + { + columnWidth: this.itemWidth, + gutter: this.gutterWidth, + ...this.options + } + ); + this.domChildren = this.getNewDomChildren(); + } + }, + getNewDomChildren() { + const node = this.$refs.waterfall; + const children = this.options && this.options.itemSelector + ? node.querySelectorAll(this.options.itemSelector) + : node.children; + return Array.prototype.slice.call(children); + } + } +}; +</script> diff --git a/src/site/components/grid/waterfall/WaterfallItem.vue b/src/site/components/grid/waterfall/WaterfallItem.vue new file mode 100644 index 0000000..2a18606 --- /dev/null +++ b/src/site/components/grid/waterfall/WaterfallItem.vue @@ -0,0 +1,10 @@ +<template> + <div class="waterfall-item"> + <slot /> + </div> +</template> +<script> +export default { + name: 'WaterfallItem' +}; +</script> |