Skip to content

feat: gridItems prop #27

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Oct 14, 2022
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ When the user scrolls inside RecycleScroller, the views are mostly just moved ar
- `items`: list of items you want to display in the scroller.
- `direction` (default: `'vertical'`): scrolling direction, either `'vertical'` or `'horizontal'`.
- `itemSize` (default: `null`): display height (or width in horizontal mode) of the items in pixels used to calculate the scroll size and position. If it is set to `null` (the default value), it will use [variable size mode](#variable-size-mode).
- `gridItems`: display that many items on the same line to create a grid. You must put a value for `itemSize` to use this prop (dynamic sizes are not supported).
- `minItemSize`: minimum size used if the height (or width in horizontal mode) of a item is unknown.
- `sizeField` (default: `'size'`): field used to get the item's size in variable size mode.
- `typeField` (default: `'type'`): field used to differentiate different kinds of components in the list. For each distinct type, a pool of recycled items will be created.
Expand Down
3 changes: 3 additions & 0 deletions docs-src/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
<router-link :to="{ name: 'chat' }">
Chat demo
</router-link>
<router-link :to="{ name: 'grid' }">
Grid demo
</router-link>
</nav>
<router-view class="page" />
</div>
Expand Down
112 changes: 112 additions & 0 deletions docs-src/src/components/GridDemo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
<script>
import { getData } from '../data'

export default {
data () {
return {
list: [],
gridItems: 6,
scrollTo: 500,
}
},

mounted () {
this.list = getData(5000)
},
}
</script>

<template>
<div class="wrapper">
<div class="toolbar">
<label>
Grid items per row
<input
v-model.number="gridItems"
type="number"
min="2"
max="20"
>
</label>
<input
v-model.number="gridItems"
type="range"
min="2"
max="20"
>
<span>
<button @mousedown="$refs.scroller.scrollToItem(scrollTo)">Scroll To: </button>
<input
v-model.number="scrollTo"
type="number"
min="0"
:max="list.length - 1"
>
</span>
</div>

<RecycleScroller
ref="scroller"
class="scroller"
:items="list"
:item-size="128"
:grid-items="gridItems"
>
<template #default="{ item, index }">
<div class="item">
<img
:key="item.id"
:src="item.value.avatar"
>
<div class="index">
{{ index }}
</div>
</div>
</template>
</RecycleScroller>
</div>
</template>

<style scoped>
.wrapper,
.scroller {
height: 100%;
}

.wrapper {
display: flex;
flex-direction: column;
}

.toolbar {
flex: none;
}

.scroller {
flex: 1;
}

.scroller >>> .hover img {
opacity: 0.5;
}

.item {
position: relative;
}

.index {
position: absolute;
top: 2px;
left: 2px;
padding: 4px;
border-radius: 4px;
background-color: rgba(255, 255, 255, 0.85);
color: black;
}

img {
width: 100%;
height: 100%;
background: #eee;
}
</style>
2 changes: 2 additions & 0 deletions docs-src/src/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import TestChat from './components/TestChat.vue'
import SimpleList from './components/SimpleList.vue'
import HorizontalDemo from './components/HorizontalDemo.vue'
import ChatDemo from './components/ChatDemo.vue'
import GridDemo from './components/GridDemo.vue'

Vue.use(VueRouter)

Expand All @@ -20,5 +21,6 @@ export default new VueRouter({
{ path: '/simple-list', name: 'simple-list', component: SimpleList },
{ path: '/horizontal', name: 'horizontal', component: HorizontalDemo },
{ path: '/chat', name: 'chat', component: ChatDemo },
{ path: '/grid', name: 'grid', component: GridDemo },
],
})
38 changes: 30 additions & 8 deletions src/components/RecycleScroller.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@
:is="itemTag"
v-for="view of pool"
:key="view.nr.id"
:style="ready ? { transform: `translate${direction === 'vertical' ? 'Y' : 'X'}(${view.position}px)` } : null"
:style="ready ? {
transform: `translate${direction === 'vertical' ? 'Y' : 'X'}(${view.position}px) translate${direction === 'vertical' ? 'X' : 'Y'}(${view.offset}px)`,
width: gridItems ? `${itemSize}px` : undefined,
height: gridItems ? `${itemSize}px` : undefined,
} : null"
class="vue-recycle-scroller__item-view"
:class="[
itemClass,
Expand Down Expand Up @@ -98,6 +102,11 @@ export default {
default: null,
},

gridItems: {
type: Number,
default: undefined,
},

minItemSize: {
type: [Number, String],
default: null,
Expand Down Expand Up @@ -214,6 +223,10 @@ export default {
},
deep: true,
},

gridItems () {
this.updateVisibleItems(true)
},
},

created () {
Expand All @@ -230,6 +243,10 @@ export default {
this.$_prerender = true
this.updateVisibleItems(false)
}

if (this.gridItems && !this.itemSize) {
console.error('[vue-recycle-scroller] You must provide an itemSize when using gridItems')
}
},

mounted () {
Expand Down Expand Up @@ -329,6 +346,7 @@ export default {

updateVisibleItems (checkItem, checkPositionDiff = false) {
const itemSize = this.itemSize
const gridItems = this.gridItems
const minItemSize = this.$_computedMinItemSize
const typeField = this.typeField
const keyField = this.simpleArray ? null : this.keyField
Expand Down Expand Up @@ -422,18 +440,20 @@ export default {
for (visibleEndIndex = visibleStartIndex; visibleEndIndex < count && (beforeSize + sizes[visibleEndIndex].accumulator) < scroll.end; visibleEndIndex++);
} else {
// Fixed size mode
startIndex = ~~(scroll.start / itemSize)
endIndex = Math.ceil(scroll.end / itemSize)
visibleStartIndex = Math.max(0, Math.floor((scroll.start - beforeSize) / itemSize))
visibleEndIndex = Math.floor((scroll.end - beforeSize) / itemSize)
startIndex = ~~(scroll.start / itemSize * gridItems)
const remainer = startIndex % gridItems
startIndex -= remainer
endIndex = Math.ceil(scroll.end / itemSize * gridItems)
visibleStartIndex = Math.max(0, Math.floor((scroll.start - beforeSize) / itemSize * gridItems))
visibleEndIndex = Math.floor((scroll.end - beforeSize) / itemSize * gridItems)

// Bounds
startIndex < 0 && (startIndex = 0)
endIndex > count && (endIndex = count)
visibleStartIndex < 0 && (visibleStartIndex = 0)
visibleEndIndex > count && (visibleEndIndex = count)

totalSize = count * itemSize
totalSize = Math.ceil(count / gridItems) * itemSize
}
}

Expand Down Expand Up @@ -547,8 +567,10 @@ export default {
// Update position
if (itemSize === null) {
view.position = sizes[i - 1].accumulator
view.offset = 0
} else {
view.position = i * itemSize
view.position = Math.floor(i / gridItems) * itemSize
view.offset = (i % gridItems) * itemSize
}
}

Expand Down Expand Up @@ -644,7 +666,7 @@ export default {
if (this.itemSize === null) {
scroll = index > 0 ? this.sizes[index - 1].accumulator : 0
} else {
scroll = index * this.itemSize
scroll = Math.floor(index / this.gridItems) * this.itemSize
}
this.scrollToPosition(scroll)
},
Expand Down