Resize on devicePixelRatio changes (#7717)

* Resize on devicePixelRatio changes
This commit is contained in:
Jukka Kurkela 2020-08-19 14:08:41 +03:00 committed by GitHub
parent 886a465c18
commit ae214d9fbd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 108 additions and 67 deletions

4
package-lock.json generated
View File

@ -2399,7 +2399,7 @@
},
"chalk": {
"version": "1.1.3",
"resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true,
"requires": {
@ -2418,7 +2418,7 @@
},
"supports-color": {
"version": "2.0.0",
"resolved": "http://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
"dev": true
}

View File

@ -176,3 +176,42 @@ export function retinaScale(chart, forceRatio) {
canvas.style.width = width + 'px';
}
}
/**
* Detects support for options object argument in addEventListener.
* https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support
* @private
*/
export const supportsEventListenerOptions = (function() {
let passiveSupported = false;
try {
const options = {
get passive() { // This function will be called when the browser attempts to access the passive property.
passiveSupported = true;
return false;
}
};
// @ts-ignore
window.addEventListener('test', null, options);
// @ts-ignore
window.removeEventListener('test', null, options);
} catch (e) {
// continue regardless of error
}
return passiveSupported;
}());
/**
* The "used" size is the final value of a dimension property after all calculations have
* been performed. This method uses the computed style of `element` but returns undefined
* if the computed style is not expressed in pixels. That can happen in some cases where
* `element` has a size relative to its parent and this last one is not yet displayed,
* for example because of `display: none` on a parent node.
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value
* @returns {number=} Size in pixels or undefined if unknown.
*/
export function readUsedSize(element, property) {
const value = getStyle(element, property);
const matches = value && value.match(/^(\d+)(\.\d+)?px$/);
return matches ? +matches[1] : undefined;
}

View File

@ -13,3 +13,26 @@ export const requestAnimFrame = (function() {
}
return window.requestAnimationFrame;
}());
/**
* Throttles calling `fn` once per animation frame
* Latest argments are used on the actual call
* @param {function} fn
* @param {*} thisArg
*/
export function throttled(fn, thisArg) {
let ticking = false;
let args = [];
return function(...rest) {
args = Array.prototype.slice.call(rest);
if (!ticking) {
ticking = true;
requestAnimFrame.call(window, () => {
ticking = false;
fn.apply(thisArg, args);
});
}
};
}

View File

@ -3,8 +3,8 @@
*/
import BasePlatform from './platform.base';
import {_getParentNode, getStyle, getRelativePosition} from '../helpers/helpers.dom';
import {requestAnimFrame} from '../helpers/helpers.extras';
import {_getParentNode, getRelativePosition, supportsEventListenerOptions, readUsedSize} from '../helpers/helpers.dom';
import {throttled} from '../helpers/helpers.extras';
import {isNullOrUndef} from '../helpers/helpers.core';
/**
@ -30,21 +30,7 @@ const EVENT_TYPES = {
pointerout: 'mouseout'
};
/**
* The "used" size is the final value of a dimension property after all calculations have
* been performed. This method uses the computed style of `element` but returns undefined
* if the computed style is not expressed in pixels. That can happen in some cases where
* `element` has a size relative to its parent and this last one is not yet displayed,
* for example because of `display: none` on a parent node.
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value
* @returns {number=} Size in pixels or undefined if unknown.
*/
function readUsedSize(element, property) {
const value = getStyle(element, property);
const matches = value && value.match(/^(\d+)(\.\d+)?px$/);
return matches ? +matches[1] : undefined;
}
const isNullOrEmpty = value => value === null || value === '';
/**
* Initializes the canvas style and render size without modifying the canvas display size,
* since responsiveness is handled by the controller.resize() method. The config is used
@ -80,14 +66,14 @@ function initCanvas(canvas, config) {
// Include possible borders in the size
style.boxSizing = style.boxSizing || 'border-box';
if (renderWidth === null || renderWidth === '') {
if (isNullOrEmpty(renderWidth)) {
const displayWidth = readUsedSize(canvas, 'width');
if (displayWidth !== undefined) {
canvas.width = displayWidth;
}
}
if (renderHeight === null || renderHeight === '') {
if (isNullOrEmpty(renderHeight)) {
if (canvas.style.height === '') {
// If no explicit render height and style height, let's apply the aspect ratio,
// which one can be specified by the user but also by charts as default option
@ -104,30 +90,6 @@ function initCanvas(canvas, config) {
return canvas;
}
/**
* Detects support for options object argument in addEventListener.
* https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support
* @private
*/
const supportsEventListenerOptions = (function() {
let passiveSupported = false;
try {
const options = {
get passive() { // This function will be called when the browser attempts to access the passive property.
passiveSupported = true;
return false;
}
};
// @ts-ignore
window.addEventListener('test', null, options);
// @ts-ignore
window.removeEventListener('test', null, options);
} catch (e) {
// continue regardless of error
}
return passiveSupported;
}());
// Default passive to true as expected by Chrome for 'touchstart' and 'touchend' events.
// https://github.com/chartjs/Chart.js/issues/4287
const eventListenerOptions = supportsEventListenerOptions ? {passive: true} : false;
@ -136,8 +98,8 @@ function addListener(node, type, listener) {
node.addEventListener(type, listener, eventListenerOptions);
}
function removeListener(node, type, listener) {
node.removeEventListener(type, listener, eventListenerOptions);
function removeListener(chart, type, listener) {
chart.canvas.removeEventListener(type, listener, eventListenerOptions);
}
function createEvent(type, chart, x, y, nativeEvent) {
@ -156,23 +118,6 @@ function fromNativeEvent(event, chart) {
return createEvent(type, chart, pos.x, pos.y, event);
}
function throttled(fn, thisArg) {
let ticking = false;
let args = [];
return function(...rest) {
args = Array.prototype.slice.call(rest);
if (!ticking) {
ticking = true;
requestAnimFrame.call(window, () => {
ticking = false;
fn.apply(thisArg, args);
});
}
};
}
function createAttachObserver(chart, type, listener) {
const canvas = chart.canvas;
const container = canvas && _getParentNode(canvas);
@ -212,6 +157,36 @@ function createDetachObserver(chart, type, listener) {
return observer;
}
const drpListeningCharts = new Map();
let oldDevicePixelRatio = 0;
function onWindowResize() {
const dpr = window.devicePixelRatio;
if (dpr === oldDevicePixelRatio) {
return;
}
oldDevicePixelRatio = dpr;
drpListeningCharts.forEach((resize, chart) => {
if (chart.currentDevicePixelRatio !== dpr) {
resize();
}
});
}
function listenDevicePixelRatioChanges(chart, resize) {
if (!drpListeningCharts.size) {
window.addEventListener('resize', onWindowResize);
}
drpListeningCharts.set(chart, resize);
}
function unlistenDevicePixelRatioChanges(chart) {
drpListeningCharts.delete(chart);
if (!drpListeningCharts.size) {
window.removeEventListener('resize', onWindowResize);
}
}
function createResizeObserver(chart, type, listener) {
const canvas = chart.canvas;
const container = canvas && _getParentNode(canvas);
@ -247,13 +222,18 @@ function createResizeObserver(chart, type, listener) {
resize(width, height);
});
observer.observe(container);
listenDevicePixelRatioChanges(chart, resize);
return observer;
}
function releaseObserver(canvas, type, observer) {
function releaseObserver(chart, type, observer) {
if (observer) {
observer.disconnect();
}
if (type === 'resize') {
unlistenDevicePixelRatioChanges(chart);
}
}
function createProxyAndListen(chart, type, listener) {
@ -367,7 +347,6 @@ export default class DomPlatform extends BasePlatform {
* @param {string} type
*/
removeEventListener(chart, type) {
const canvas = chart.canvas;
const proxies = chart.$proxies || (chart.$proxies = {});
const proxy = proxies[type];
@ -381,7 +360,7 @@ export default class DomPlatform extends BasePlatform {
resize: releaseObserver
};
const handler = handlers[type] || removeListener;
handler(canvas, type, proxy);
handler(chart, type, proxy);
proxies[type] = undefined;
}