This function determines which images should be loaded based on which image is currently in the viewport (array index), what direction the user is scrolling, and if the image has yet to be loaded. It has a bias for what direction the user is scrolling, and proximity to the current image in viewport (delta). It attempts to match the two closest elements before and after.
The logic is mostly a set of ugly if
statements, which I hope could be improved upon. Perhaps here is an opportunity for me to learn and implement a new design pattern or algorithm?
Basic example of matches.
Legend: _ = images
, 0 = array index
(image in viewport), X = matches
(image to load)
0 X _ _ _ _ _
0 X X _ _ _ _
X 0 X _ _ _ _
X X 0 X X _ _
_ X X 0 X X _
_ _ X X 0 X X
_ _ _ X X 0 X
_ _ _ _ X X 0
_ _ _ _ _ X 0
Above, it will match the two closest elements (if available) before and after the index given the delta (distance) from the index is the same for the before and after elements.
If the user is scrolling, there is a bias towards the direction they are scrolling if the delta between the before and after elements is not equal. Eg. Using 30% bias, If scrolling right, and the closest right-side elements are 12 and 13 units away, and the closest left-side element is 10 units away we only load the 12 and 13 elements.
Y = Miss
(closets element on respective before/after, but not matched due to scroll bias), --> = scroll direction
--> _ _ _ _ _ _ X X 0 X X _ _ _ _ _ _ _
--> _ _ _ _ Y Y _ _ _ 0 _ X X _ _ _ _ _
--> _ _ _ _ Y Y _ _ _ _ 0 _ _ X X _ _ _
--> _ _ _ _ Y Y _ _ _ _ 0 _ _ _ _ X X _
<-- _ _ _ _ X X _ _ _ 0 _ _ _ _ _ _ _ Y
<-- _ _ X X _ _ _ _ 0 _ _ _ _ _ _ _ _ Y
<-- X X _ _ _ _ _ _ 0 _ _ _ _ _ _ _ _ Y
Code:
queueClosestComponent(){
//find our location in the array
let lastIndex = this.components.indexOf(this.lastComponentInViewport);
//creates an array of the closet items and their relative position to the last image in the viewport
let mapped = this.components
.map(function(item, index) {
//map the closeness of each item
return {
delta: Math.abs(lastIndex - index),
component: item,
position: Math.sign(lastIndex - index) === 1 ? "previous" : "next"
};
}, this)
.filter(function(item) {
//remove items that are loaded or currently loading
if (item.component.loadingState === null) {
return true;
}
})
.sortBy("delta");
let closestNext = mapped.filterBy("position", "next").firstObject;
let closestPrevious = mapped.filterBy("position", "previous").firstObject;
//second closest
let closest2ndNext = mapped.filterBy("position", "next").objectAt(1);
let closest2ndPrevious = mapped
.filterBy("position", "previous")
.objectAt(1);
//if there is no next or previous
if (!closestNext && !closestPrevious) {
return;
}
//if there is no previous
if (closestNext && !closestPrevious) {
//console.log("ADD: no previous: loading next");
this.addToQueue("background", closestNext.component);
if (closest2ndNext)
this.addToQueue("background", closest2ndNext.component);
return;
} else if (!closestNext && closestPrevious) {
//if there is no next
//console.log("ADD: no next: loading previous");
this.addToQueue("background", closestPrevious.component);
if (closest2ndPrevious)
this.addToQueue("background", closest2ndPrevious.component);
return;
}
//if next and previous are the same delta, and within 3 items away
if (
closestNext.delta === closestPrevious.delta &&
closestNext.delta <= 3
) {
//console.log("ADD: same delta");
this.addToQueue("background", closestNext.component);
this.addToQueue("background", closestPrevious.component);
return;
}
//when scrolling, give a preference to the item in the given direction, but regardless still try to load the closest
let directionalFavour = 1.3; //30% favour to the given direction
if (
closestNext.delta / directionalFavour <= closestPrevious.delta &&
this.direction === "next"
) {
//console.log("ADD: right/down next");
this.addToQueue("background", closestNext.component);
if (closest2ndNext)
this.addToQueue("background", closest2ndNext.component);
return;
} else if (
closestNext.delta / directionalFavour >= closestPrevious.delta &&
this.direction === "next"
) {
//console.log("ADD: right/down previous");
this.addToQueue("background", closestPrevious.component);
return;
}
if (
closestNext.delta >= closestPrevious.delta / directionalFavour &&
this.direction === "previous"
) {
this.addToQueue("background", closestPrevious.component);
if (closest2ndPrevious)
this.addToQueue("background", closest2ndPrevious.component);
//console.log("ADD: left/up previous ");
return;
} else if (
closestNext.delta <= closestPrevious.delta / directionalFavour &&
this.direction === "previous"
) {
//console.log("ADD: left/up next ");
this.addToQueue("background", closestNext.component);
return;
}
}