aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/site/components/grid/Grid.vue5
-rw-r--r--src/site/components/grid/waterfall/Waterfall.vue266
-rw-r--r--src/site/components/grid/waterfall/WaterfallItem.vue72
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>