diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/site/components/grid/Grid.vue | 5 | ||||
| -rw-r--r-- | src/site/components/grid/waterfall/Waterfall.vue | 266 | ||||
| -rw-r--r-- | src/site/components/grid/waterfall/WaterfallItem.vue | 72 |
3 files changed, 99 insertions, 244 deletions
diff --git a/src/site/components/grid/Grid.vue b/src/site/components/grid/Grid.vue index e6a8c64..f98108e 100644 --- a/src/site/components/grid/Grid.vue +++ b/src/site/components/grid/Grid.vue @@ -25,6 +25,7 @@ v-if="showWaterfall" :gutterWidth="10" :gutterHeight="4" + :options="{fitWidth: true}" :itemWidth="width" :items="gridFiles"> <template v-slot="{item}"> @@ -451,6 +452,10 @@ div.actions { display: none; } +.waterfall { + margin: 0 auto; +} + .waterfall-item:hover { div.actions { opacity: 1; diff --git a/src/site/components/grid/waterfall/Waterfall.vue b/src/site/components/grid/waterfall/Waterfall.vue index a83a275..79a330a 100644 --- a/src/site/components/grid/waterfall/Waterfall.vue +++ b/src/site/components/grid/waterfall/Waterfall.vue @@ -1,51 +1,20 @@ <template> - <div class="waterfall"> - <WaterfallItem v-for="(item, index) in items" :key="item.id" :ref="`item-${item.id}`" :width="itemWidth"> + <div ref="waterfall" class="waterfall"> + <WaterfallItem + v-for="(item, index) 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 quickSort = (arr, type) => { - const left = []; - const right = []; - if (arr.length <= 1) { - return arr; - } - const povis = arr[0]; - for (let i = 1; i < arr.length; i++) { - if (arr[i][type] < povis[type]) { - left.push(arr[i]); - } else { - right.push(arr[i]); - } - } - return quickSort(left, type).concat(povis, quickSort(right, type)); -}; - -const getMinIndex = (arr) => { - let pos = 0; - for (let i = 0; i < arr.length; i++) { - if (arr[pos] > arr[i]) { - pos = i; - } - } - return pos; -}; - -const _ = { - on(el, type, func, capture = false) { - el.addEventListener(type, func, capture); - }, - off(el, type, func, capture = false) { - el.removeEventListener(type, func, capture); - }, -}; - -const sum = (arr) => arr.reduce((acc, val) => acc + val, 0); +const isBrowser = typeof window !== 'undefined'; +const Masonry = isBrowser ? window.Masonry || require('masonry-layout') : null; +const imagesloaded = isBrowser ? require('imagesloaded') : null; export default { name: 'Waterfall', @@ -53,159 +22,112 @@ export default { WaterfallItem, }, props: { - gutterWidth: { - type: Number, - default: 0, - }, - gutterHeight: { - type: Number, - default: 0, - }, - resizable: { - type: Boolean, - default: true, - }, - align: { - type: String, - default: 'center', - }, - fixWidth: { - type: Number, - default: 0, - }, - minCol: { - type: Number, - default: 1, - }, - maxCol: { - type: Number, - default: 0, + options: { + type: Object, + default: () => {}, }, - percent: { + items: { type: Array, - default: null, + default: () => [], }, itemWidth: { type: Number, default: 150, }, - items: { - type: Array, - default: () => [], + gutterWidth: { + type: Number, + default: 10, }, - }, - data() { - return { - timer: null, - colNum: 0, - lastWidth: 0, - percentWidthArr: [], - readyChildCount: 0, - }; - }, - watch: { - items() { - this.$nextTick(() => this.render('watch')); + gutterHeight: { + type: Number, + default: 4, }, }, - created() { - this.$on('itemRender', () => { - if (this.timer) { - clearTimeout(this.timer); - } - this.timer = setTimeout(() => { - this.render('created'); - }, 0); - }); - }, mounted() { - this.resizeHandle(); - this.$watch('resizable', this.resizeHandle); + this.initializeMasonry(); + this.imagesLoaded(); + }, + updated() { + this.performLayout(); + this.imagesLoaded(); }, - beforeDestroy() { - this.$off('itemRender'); - _.off(window, 'resize', this.render); + unmounted() { + this.masonry.destroy(); }, methods: { - calulate(arr) { - const pageWidth = this.fixWidth ? this.fixWidth : this.$el.offsetWidth; - // 百分比布局计算 - if (this.percent) { - this.colNum = this.percent.length; - const total = sum(this.percent); - this.percentWidthArr = this.percent.map((value) => (value / total) * pageWidth); - this.lastWidth = 0; - // 正常布局计算 - } else { - this.colNum = parseInt(pageWidth / (arr.width + this.gutterWidth), 10); - if (this.minCol && this.colNum < this.minCol) { - this.colNum = this.minCol; - this.lastWidth = 0; - } else if (this.maxCol && this.colNum > this.maxCol) { - this.colNum = this.maxCol; - this.lastWidth = pageWidth - (arr.width + this.gutterWidth) * this.colNum + this.gutterWidth; - } else { - this.lastWidth = pageWidth - (arr.width + this.gutterWidth) * this.colNum + this.gutterWidth; - } + 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(); }, - resizeHandle() { - if (this.resizable) { - _.on(window, 'resize', this.render, false); - } else { - _.off(window, 'resize', this.render, false); + diffDomChildren() { + const oldChildren = this.domChildren.filter((element) => !!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, + }; }, - render(context) { - console.log(context); - if (!this.items) return; - // 重新排序 - let childArr = []; - childArr = this.items.map(({ id }) => this.$refs[`item-${id}`][0].getMeta()); - childArr = quickSort(childArr, 'order'); - // 计算列数 - this.calulate(childArr[0]); - const offsetArr = Array(this.colNum).fill(0); - // 渲染 - childArr.forEach((child) => { - const position = getMinIndex(offsetArr); - // 百分比布局渲染 - if (this.percent) { - let left = 0; - child.el.style.width = `${this.percentWidthArr[position]}px`; - if (position === 0) { - left = 0; - } else { - for (let i = 0; i < position; i++) { - left += this.percentWidthArr[i]; - } - } - child.el.style.left = `${left}px`; - // 正常布局渲染 - } else { - if (this.align === 'left') { // eslint-disable-line no-lonely-if - child.el.style.left = `${position * (child.width + this.gutterWidth)}px`; - } else if (this.align === 'right') { - child.el.style.left = `${position * (child.width + this.gutterWidth) + this.lastWidth}px`; - } else { - child.el.style.left = `${position * (child.width + this.gutterWidth) + this.lastWidth / 2}px`; - } - } - if (child.height === 0) { - return; - } - child.el.style.top = `${offsetArr[position]}px`; - offsetArr[position] += child.height + this.gutterHeight; - this.$el.style.height = `${Math.max(...offsetArr)}px`; - }); - this.$emit('rendered', this); + 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> -<style> - .waterfall { - position: relative; - } +<style lang="scss" scoped> +.wfi { + +} </style> diff --git a/src/site/components/grid/waterfall/WaterfallItem.vue b/src/site/components/grid/waterfall/WaterfallItem.vue index a49c58c..c5cade1 100644 --- a/src/site/components/grid/waterfall/WaterfallItem.vue +++ b/src/site/components/grid/waterfall/WaterfallItem.vue @@ -3,80 +3,8 @@ <slot /> </div> </template> - <script> -import imagesLoaded from 'imagesloaded'; - export default { name: 'WaterfallItem', - props: { - order: { - type: Number, - default: 0, - }, - width: { - type: Number, - default: 150, - }, - video: { - type: Boolean, - default: false, - }, - }, - data() { - return { - itemWidth: 0, - height: 0, - }; - }, - created() { - this.$watch(() => this.height, this.emit); - }, - mounted() { - this.$el.style.display = 'none'; - this.$el.style.width = `${this.width}px`; - this.emit(); - if (this.video) { - // find first video object - const videoEl = this.$slots.default.find((e) => e.tag?.toLowerCase() === 'video'); - const el = videoEl.elm; - - // add event listener for video loaded - el.onloadeddata = () => { - this.$el.style.left = '-9999px'; - this.$el.style.top = '-9999px'; - this.$el.style.display = 'block'; - this.height = el.offsetHeight + 5; - this.itemWidth = el.offsetWidth; - }; - } else { - imagesLoaded(this.$el, () => { - this.$el.style.left = '-9999px'; - this.$el.style.top = '-9999px'; - this.$el.style.display = 'block'; - this.height = this.$el.offsetHeight; - this.itemWidth = this.$el.offsetWidth; - }); - } - }, - methods: { - emit() { - this.$parent.$emit('itemRender'); - }, - getMeta() { - return { - el: this.$el, - height: this.height, - width: this.itemWidth, - order: this.order, - }; - }, - }, }; </script> - -<style scoped> - .waterfall-item { - position: absolute; - } -</style> |