Skip to content

Commit

Permalink
Watch for all scrollable parents
Browse files Browse the repository at this point in the history
Since scrolling doesn't happen only on the body (window), make sure to listen for scroll events for all parent elements that can be scrolled.
  • Loading branch information
simonbrunel committed Oct 22, 2016
1 parent f428aeb commit ae70554
Showing 1 changed file with 104 additions and 29 deletions.
133 changes: 104 additions & 29 deletions src/chart.deferred.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

var Chart = window.Chart;
var helpers = Chart.helpers;
var STUB_KEY = '_chartjs_deferred';
var MODEL_KEY = '_deferred_model';

/**
* Plugin based on discussion from Chart.js issue #2745.
Expand All @@ -19,17 +21,20 @@
};

// DOM implementation
// @todo move it in Chart.js: src/core/core.platform.js
// @TODO move it in Chart.js: src/core/core.platform.js
Chart.platform = helpers.extend(Chart.platform || {}, {
defer: function(fn, delay, scope) {
window.setTimeout(function() {
fn.call(scope)
}, delay);
var callback = function() {
fn.call(scope);
};
if (!delay) {
helpers.requestAnimFrame.call(window, callback);
} else {
window.setTimeout(callback, delay);
}
}
});

var deferred_instances = [];

function computeOffset(value, base) {
var number = parseInt(value, 10);
if (isNaN(number)) {
Expand All @@ -42,8 +47,14 @@
}

function chartInViewport(instance) {
var model = instance[MODEL_KEY];
var canvas = instance.chart.canvas;
var model = instance._deferred_model;

// http://stackoverflow.com/a/21696585
if (canvas.offsetParent === null) {
return false;
}

var rect = canvas.getBoundingClientRect();
var dy = computeOffset(model.yOffset || 0, rect.height);
var dx = computeOffset(model.xOffset || 0, rect.width);
Expand All @@ -54,19 +65,6 @@
&& rect.top + dy <= window.innerHeight;
}

helpers.addEvent(window, 'scroll', function() {
var instances = deferred_instances.slice(0);
var ilen = instances.length;
var instance, i;

for (i=0; i<ilen; ++i) {
instance = instances[i];
if (chartInViewport(instance)) {
instance.update();
}
}
});

function buildDeferredModel(instance) {
var defaults = Chart.Deferred.defaults;
var options = instance.options.deferred;
Expand All @@ -84,31 +82,108 @@
xOffset: getValue(options.xOffset, defaults.xOffset),
yOffset: getValue(options.yOffset, defaults.yOffset),
delay: getValue(options.delay, defaults.delay),
appeared: false,
delayed: false,
loaded: false
loaded: false,
elements: []
};
}

function onScroll(event) {
var node = event.target;
var stub = node[STUB_KEY];
if (stub.ticking) {
return;
}

stub.ticking = true;
Chart.platform.defer(function() {
var instances = stub.instances.slice();
var ilen = instances.length;
var instance, i;

for (i=0; i<ilen; ++i) {
instance = instances[i];
if (chartInViewport(instance)) {
unwatch(instance);
instance[MODEL_KEY].appeared = true;
instance.update();
}
}

stub.ticking = false;
});
}

function isScrollable(node) {
var type = node.nodeType;
if (type === Node.ELEMENT_NODE) {
var overflowX = helpers.getStyle(node, 'overflow-x');
var overflowY = helpers.getStyle(node, 'overflow-y');
return overflowX === 'auto' || overflowX === 'scroll'
|| overflowY === 'auto' || overflowY === 'scroll';
}

return node.nodeType === Node.DOCUMENT_NODE;
}

function watch(instance) {
var canvas = instance.chart.canvas;
var parent = canvas.parentElement;
var stub, instances;

while (parent) {
if (isScrollable(parent)) {
stub = parent[STUB_KEY] || (parent[STUB_KEY] = {});
instances = stub.instances || (stub.instances = []);
if (instances.length === 0) {
parent.addEventListener('scroll', onScroll, { passive: true });
}

instances.push(instance);
instance[MODEL_KEY].elements.push(parent);
}

parent = parent.parentElement || parent.ownerDocument;
}
}

function unwatch(instance) {
instance[MODEL_KEY].elements.forEach(function(element) {
var instances = element[STUB_KEY].instances;
instances.splice(instances.indexOf(instance), 1);
if (!instances.length) {
helpers.removeEvent(element, 'scroll', onScroll);
delete element[STUB_KEY];
}
});

instance[MODEL_KEY].elements = [];
}

Chart.plugins.register({
beforeInit: function(instance) {
instance._deferred_model = buildDeferredModel(instance);
deferred_instances.push(instance);
var model = instance[MODEL_KEY] = buildDeferredModel(instance);
if (model.enabled) {
watch(instance);
}
},

beforeDatasetsUpdate: function(instance) {
var model = instance._deferred_model;
var index;
var model = instance[MODEL_KEY];
if (!model.enabled) {
return true;
}

if (!model.loaded) {
if (model.enabled && !chartInViewport(instance)) {
if (!model.appeared && !chartInViewport(instance)) {
// cancel the datasets update
return false;
}

// avoid extra scroll update by removing the chart from the lazy instances.
index = deferred_instances.indexOf(instance);
deferred_instances.splice(index, 1);
model.appeared = true;
model.loaded = true;
unwatch(instance);

if (model.delay > 0) {
model.delayed = true;
Expand Down

0 comments on commit ae70554

Please sign in to comment.