Add `isCentered` option, which mostly works.

pull/160/head
Glen Cheney 7 years ago
parent 533716bd40
commit d08e1e3e9d

207
dist/shuffle.js vendored

@ -280,6 +280,52 @@ var Point = function () {
return Point;
}();
var Rect = function () {
/**
* Class for representing rectangular regions.
* https://github.com/google/closure-library/blob/master/closure/goog/math/rect.js
* @param {number} x Left.
* @param {number} y Top.
* @param {number} w Width.
* @param {number} h Height.
* @param {number} id Identifier
* @constructor
*/
function Rect(x, y, w, h, id) {
classCallCheck(this, Rect);
this.id = id;
/** @type {number} */
this.left = x;
/** @type {number} */
this.top = y;
/** @type {number} */
this.width = w;
/** @type {number} */
this.height = h;
}
/**
* Returns whether two rectangles intersect.
* @param {Rect} a A Rectangle.
* @param {Rect} b A Rectangle.
* @return {boolean} Whether a and b intersect.
*/
createClass(Rect, null, [{
key: "intersects",
value: function intersects(a, b) {
return a.left < b.left + b.width && b.left < a.left + a.width && a.top < b.top + b.height && b.top < a.top + a.height;
}
}]);
return Rect;
}();
var Classes = {
BASE: 'shuffle',
SHUFFLE_ITEM: 'shuffle-item',
@ -1248,12 +1294,13 @@ var Shuffle = function (_TinyEmitter) {
value: function _layout(items) {
var _this4 = this;
var itemPositions = this._getNextPositions(items);
var count = 0;
items.forEach(function (item) {
items.forEach(function (item, i) {
var currPos = item.point;
var currScale = item.scale;
var itemSize = Shuffle.getSize(item.element, true);
var pos = _this4._getItemPosition(itemSize);
var nextPosition = itemPositions[i];
function callback() {
item.element.style.transitionDelay = '';
@ -1262,13 +1309,13 @@ var Shuffle = function (_TinyEmitter) {
// If the item will not change its position, do not add it to the render
// queue. Transitions don't fire when setting a property to the same value.
if (Point.equals(currPos, pos) && currScale === ShuffleItem.Scale.VISIBLE) {
if (Point.equals(currPos, nextPosition) && currScale === ShuffleItem.Scale.VISIBLE) {
item.applyCss(ShuffleItem.Css.VISIBLE.before);
callback();
return;
}
item.point = pos;
item.point = nextPosition;
item.scale = ShuffleItem.Scale.VISIBLE;
// Clone the object so that the `before` object isn't modified when the
@ -1286,6 +1333,38 @@ var Shuffle = function (_TinyEmitter) {
});
}
/**
* Return an array of Point instances representing the future positions of
* each item.
* @param {Array.<ShuffleItem>} items Array of sorted shuffle items.
* @return {Array.<Point>}
* @private
*/
}, {
key: '_getNextPositions',
value: function _getNextPositions(items) {
var _this5 = this;
// If position data is going to be changed, add the item's size to the
// transformer to allow for calculations.
if (this.options.isCentered) {
var itemsData = items.map(function (item, i) {
var itemSize = Shuffle.getSize(item.element, true);
var point = _this5._getItemPosition(itemSize);
return new Rect(point.x, point.y, itemSize.width, itemSize.height, i);
});
return this.getTransformedPositions(itemsData);
}
// If no transforms are going to happen, simply return an array of the
// future points of each item.
return items.map(function (item) {
return _this5._getItemPosition(Shuffle.getSize(item.element, true));
});
}
/**
* Determine the location of the next item, based on its size.
* @param {{width: number, height: number}} itemSize Object with width and height.
@ -1306,6 +1385,84 @@ var Shuffle = function (_TinyEmitter) {
});
}
/**
* Mutate positions before they're applied. This method attempts to center
* items, however, it does not work with column-spanning items.
* @param {Array.<Rect>} itemRects Item data objects.
* @return {Array.<Point>}
* @protected
*/
}, {
key: 'getTransformedPositions',
value: function getTransformedPositions(itemRects) {
var _this6 = this;
var rows = {};
// Populate rows by their offset because items could jump between rows like:
// a c
// bbb
itemRects.forEach(function (itemRect) {
if (rows[itemRect.top]) {
// Push the point to the last row array.
rows[itemRect.top].push(itemRect);
} else {
// Start of a new row.
rows[itemRect.top] = [itemRect];
}
});
// For each row, find the end of the last item, then calculate
// the remaining space by dividing it by 2. Then add that
// offset to the x position of each point.
var rects = [];
var centered = Object.keys(rows).map(function (key) {
var itemRects = rows[key];
var lastItem = itemRects[itemRects.length - 1];
var end = lastItem.left + lastItem.width;
var offset = Math.round((_this6.containerWidth - end) / 2);
var newRects = [];
var ok = itemRects.every(function (r) {
var newRect = new Rect(r.left + offset, r.top, r.width, r.height, r.id);
// Check all current rects to make sure none overlap.
var noOverlap = rects.every(function (rect) {
return !Rect.intersects(newRect, rect);
});
if (noOverlap) {
newRects.push(newRect);
}
return noOverlap;
});
// This assumes that the original position will be okay, even if other
// items are moved above it, which can lead to overlaps. In order to fix
// this, getItemPosition would need to realize when it goes to a new row
// and adjust the items above it before choosing a position for the current
// item...
// If none of the rectangles overlapped, the whole group can be centered.
var finalRects = ok ? newRects : itemRects;
rects = rects.concat(finalRects);
return finalRects;
});
// Reduce array of arrays to a single array of points.
// https://stackoverflow.com/a/10865042/373422
// Then reset sort back to how the items were passed to this method.
// Remove the wrapper object with index, map to a Point.
return [].concat.apply([], centered) // eslint-disable-line prefer-spread
.sort(function (a, b) {
return a.id - b.id;
}).map(function (itemRect) {
return new Point(itemRect.left, itemRect.top);
});
}
/**
* Hides the elements that don't match our filter.
* @param {Array.<ShuffleItem>} collection Collection to shrink.
@ -1315,7 +1472,7 @@ var Shuffle = function (_TinyEmitter) {
}, {
key: '_shrink',
value: function _shrink() {
var _this5 = this;
var _this7 = this;
var collection = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this._getConcealedItems();
@ -1340,9 +1497,9 @@ var Shuffle = function (_TinyEmitter) {
item.scale = ShuffleItem.Scale.HIDDEN;
var styles = Object.assign({}, ShuffleItem.Css.HIDDEN.before);
styles.transitionDelay = _this5._getStaggerAmount(count) + 'ms';
styles.transitionDelay = _this7._getStaggerAmount(count) + 'ms';
_this5._queue.push({
_this7._queue.push({
item: item,
styles: styles,
callback: callback
@ -1427,11 +1584,11 @@ var Shuffle = function (_TinyEmitter) {
}, {
key: '_getTransitionFunction',
value: function _getTransitionFunction(opts) {
var _this6 = this;
var _this8 = this;
return function (done) {
opts.item.applyCss(_this6.getStylesForTransition(opts));
_this6._whenTransitionDone(opts.item.element, opts.callback, done);
opts.item.applyCss(_this8.getStylesForTransition(opts));
_this8._whenTransitionDone(opts.item.element, opts.callback, done);
};
}
@ -1476,14 +1633,14 @@ var Shuffle = function (_TinyEmitter) {
}, {
key: '_startTransitions',
value: function _startTransitions(transitions) {
var _this7 = this;
var _this9 = this;
// Set flag that shuffle is currently in motion.
this.isTransitioning = true;
// Create an array of functions to be called.
var callbacks = transitions.map(function (obj) {
return _this7._getTransitionFunction(obj);
return _this9._getTransitionFunction(obj);
});
index$3(callbacks, this._movementFinished.bind(this));
@ -1510,7 +1667,7 @@ var Shuffle = function (_TinyEmitter) {
}, {
key: '_styleImmediately',
value: function _styleImmediately(objects) {
var _this8 = this;
var _this10 = this;
if (objects.length) {
var elements = objects.map(function (obj) {
@ -1519,7 +1676,7 @@ var Shuffle = function (_TinyEmitter) {
Shuffle._skipTransitions(elements, function () {
objects.forEach(function (obj) {
obj.item.applyCss(_this8.getStylesForTransition(obj));
obj.item.applyCss(_this10.getStylesForTransition(obj));
obj.callback();
});
});
@ -1687,7 +1844,7 @@ var Shuffle = function (_TinyEmitter) {
}, {
key: 'remove',
value: function remove(elements) {
var _this9 = this;
var _this11 = this;
if (!elements.length) {
return;
@ -1696,20 +1853,20 @@ var Shuffle = function (_TinyEmitter) {
var collection = arrayUnique(elements);
var oldItems = collection.map(function (element) {
return _this9.getItemByElement(element);
return _this11.getItemByElement(element);
}).filter(function (item) {
return !!item;
});
var handleLayout = function handleLayout() {
_this9._disposeItems(oldItems);
_this11._disposeItems(oldItems);
// Remove the collection in the callback
collection.forEach(function (element) {
element.parentNode.removeChild(element);
});
_this9._dispatch(Shuffle.EventType.REMOVED, { collection: collection });
_this11._dispatch(Shuffle.EventType.REMOVED, { collection: collection });
};
// Hide collection first.
@ -1754,7 +1911,7 @@ var Shuffle = function (_TinyEmitter) {
}, {
key: 'resetItems',
value: function resetItems() {
var _this10 = this;
var _this12 = this;
// Remove refs to current items.
this._disposeItems(this.items);
@ -1768,8 +1925,8 @@ var Shuffle = function (_TinyEmitter) {
this.once(Shuffle.EventType.LAYOUT, function () {
// Add transition to each item.
_this10.setItemTransitions(_this10.items);
_this10.isInitialized = true;
_this12.setItemTransitions(_this12.items);
_this12.isInitialized = true;
});
// Lay out all items.
@ -1981,11 +2138,15 @@ Shuffle.options = {
// Affects using an array with filter. e.g. `filter(['one', 'two'])`. With "any",
// the element passes the test if any of its groups are in the array. With "all",
// the element only passes if all groups are in the array.
filterMode: Shuffle.FilterMode.ANY
filterMode: Shuffle.FilterMode.ANY,
// Whether to center grid items in the row with the leftover space.
isCentered: false
};
// Expose for testing. Hack at your own risk.
Shuffle.__Point = Point;
Shuffle.__Rect = Rect;
Shuffle.__sorter = sorter;
Shuffle.__getColumnSpan = getColumnSpan;
Shuffle.__getAvailablePositions = getAvailablePositions;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1,39 @@
export default class Rect {
/**
* Class for representing rectangular regions.
* https://github.com/google/closure-library/blob/master/closure/goog/math/rect.js
* @param {number} x Left.
* @param {number} y Top.
* @param {number} w Width.
* @param {number} h Height.
* @param {number} id Identifier
* @constructor
*/
constructor(x, y, w, h, id) {
this.id = id;
/** @type {number} */
this.left = x;
/** @type {number} */
this.top = y;
/** @type {number} */
this.width = w;
/** @type {number} */
this.height = h;
}
/**
* Returns whether two rectangles intersect.
* @param {Rect} a A Rectangle.
* @param {Rect} b A Rectangle.
* @return {boolean} Whether a and b intersect.
*/
static intersects(a, b) {
return (
a.left < b.left + b.width && b.left < a.left + a.width &&
a.top < b.top + b.height && b.top < a.top + a.height);
}
}

@ -4,6 +4,7 @@ import throttle from 'throttleit';
import parallel from 'array-parallel';
import Point from './point';
import Rect from './rect';
import ShuffleItem from './shuffle-item';
import Classes from './classes';
import getNumberStyle from './get-number-style';
@ -486,12 +487,13 @@ class Shuffle extends TinyEmitter {
* out in order in their array.
*/
_layout(items) {
const itemPositions = this._getNextPositions(items);
let count = 0;
items.forEach((item) => {
items.forEach((item, i) => {
const currPos = item.point;
const currScale = item.scale;
const itemSize = Shuffle.getSize(item.element, true);
const pos = this._getItemPosition(itemSize);
const nextPosition = itemPositions[i];
function callback() {
item.element.style.transitionDelay = '';
@ -500,13 +502,13 @@ class Shuffle extends TinyEmitter {
// If the item will not change its position, do not add it to the render
// queue. Transitions don't fire when setting a property to the same value.
if (Point.equals(currPos, pos) && currScale === ShuffleItem.Scale.VISIBLE) {
if (Point.equals(currPos, nextPosition) && currScale === ShuffleItem.Scale.VISIBLE) {
item.applyCss(ShuffleItem.Css.VISIBLE.before);
callback();
return;
}
item.point = pos;
item.point = nextPosition;
item.scale = ShuffleItem.Scale.VISIBLE;
// Clone the object so that the `before` object isn't modified when the
@ -524,6 +526,31 @@ class Shuffle extends TinyEmitter {
});
}
/**
* Return an array of Point instances representing the future positions of
* each item.
* @param {Array.<ShuffleItem>} items Array of sorted shuffle items.
* @return {Array.<Point>}
* @private
*/
_getNextPositions(items) {
// If position data is going to be changed, add the item's size to the
// transformer to allow for calculations.
if (this.options.isCentered) {
const itemsData = items.map((item, i) => {
const itemSize = Shuffle.getSize(item.element, true);
const point = this._getItemPosition(itemSize);
return new Rect(point.x, point.y, itemSize.width, itemSize.height, i);
});
return this.getTransformedPositions(itemsData);
}
// If no transforms are going to happen, simply return an array of the
// future points of each item.
return items.map(item => this._getItemPosition(Shuffle.getSize(item.element, true)));
}
/**
* Determine the location of the next item, based on its size.
* @param {{width: number, height: number}} itemSize Object with width and height.
@ -541,6 +568,74 @@ class Shuffle extends TinyEmitter {
});
}
/**
* Mutate positions before they're applied. This method attempts to center
* items, however, it does not work with column-spanning items.
* @param {Array.<Rect>} itemRects Item data objects.
* @return {Array.<Point>}
* @protected
*/
getTransformedPositions(itemRects) {
const rows = {};
// Populate rows by their offset because items could jump between rows like:
// a c
// bbb
itemRects.forEach((itemRect) => {
if (rows[itemRect.top]) {
// Push the point to the last row array.
rows[itemRect.top].push(itemRect);
} else {
// Start of a new row.
rows[itemRect.top] = [itemRect];
}
});
// For each row, find the end of the last item, then calculate
// the remaining space by dividing it by 2. Then add that
// offset to the x position of each point.
let rects = [];
const centered = Object.keys(rows).map((key) => {
const itemRects = rows[key];
const lastItem = itemRects[itemRects.length - 1];
const end = lastItem.left + lastItem.width;
const offset = Math.round((this.containerWidth - end) / 2);
const newRects = [];
const ok = itemRects.every((r) => {
const newRect = new Rect(r.left + offset, r.top, r.width, r.height, r.id);
// Check all current rects to make sure none overlap.
const noOverlap = rects.every(rect => !Rect.intersects(newRect, rect));
if (noOverlap) {
newRects.push(newRect);
}
return noOverlap;
});
// This assumes that the original position will be okay, even if other
// items are moved above it, which can lead to overlaps. In order to fix
// this, getItemPosition would need to realize when it goes to a new row
// and adjust the items above it before choosing a position for the current
// item...
// If none of the rectangles overlapped, the whole group can be centered.
const finalRects = ok ? newRects : itemRects;
rects = rects.concat(finalRects);
return finalRects;
});
// Reduce array of arrays to a single array of points.
// https://stackoverflow.com/a/10865042/373422
// Then reset sort back to how the items were passed to this method.
// Remove the wrapper object with index, map to a Point.
return [].concat.apply([], centered) // eslint-disable-line prefer-spread
.sort((a, b) => (a.id - b.id))
.map(itemRect => new Point(itemRect.left, itemRect.top));
}
/**
* Hides the elements that don't match our filter.
* @param {Array.<ShuffleItem>} collection Collection to shrink.
@ -1117,10 +1212,14 @@ Shuffle.options = {
// the element passes the test if any of its groups are in the array. With "all",
// the element only passes if all groups are in the array.
filterMode: Shuffle.FilterMode.ANY,
// Whether to center grid items in the row with the leftover space.
isCentered: false,
};
// Expose for testing. Hack at your own risk.
Shuffle.__Point = Point;
Shuffle.__Rect = Rect;
Shuffle.__sorter = sorter;
Shuffle.__getColumnSpan = getColumnSpan;
Shuffle.__getAvailablePositions = getAvailablePositions;

Loading…
Cancel
Save