Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
META-INF.assets.rjzjh.zrender.Painter.js Maven / Gradle / Ivy
/**
* Default canvas painter
* @module zrender/Painter
* @author Kener (@Kener-林峰, [email protected] )
* errorrik ([email protected] )
* pissang (https://www.github.com/pissang)
*/
define(function (require) {
'use strict';
var config = require('./config');
var util = require('./core/util');
var log = require('./core/log');
var BoundingRect = require('./core/BoundingRect');
var Layer = require('./Layer');
var requestAnimationFrame = require('./animation/requestAnimationFrame');
// PENDIGN
// Layer exceeds MAX_PROGRESSIVE_LAYER_NUMBER may have some problem when flush directly second time.
var MAX_PROGRESSIVE_LAYER_NUMBER = 5;
function parseInt10(val) {
return parseInt(val, 10);
}
function isLayerValid(layer) {
if (!layer) {
return false;
}
if (layer.isBuildin) {
return true;
}
if (typeof(layer.resize) !== 'function'
|| typeof(layer.refresh) !== 'function'
) {
return false;
}
return true;
}
function preProcessLayer(layer) {
layer.__unusedCount++;
}
function postProcessLayer(layer) {
if (layer.__unusedCount == 1) {
layer.clear();
}
}
var tmpRect = new BoundingRect(0, 0, 0, 0);
var viewRect = new BoundingRect(0, 0, 0, 0);
function isDisplayableCulled(el, width, height) {
tmpRect.copy(el.getBoundingRect());
if (el.transform) {
tmpRect.applyTransform(el.transform);
}
viewRect.width = width;
viewRect.height = height;
return !tmpRect.intersect(viewRect);
}
function isClipPathChanged(clipPaths, prevClipPaths) {
if (!clipPaths || !prevClipPaths || (clipPaths.length !== prevClipPaths.length)) {
return true;
}
for (var i = 0; i < clipPaths.length; i++) {
if (clipPaths[i] !== prevClipPaths[i]) {
return true;
}
}
}
function doClip(clipPaths, ctx) {
for (var i = 0; i < clipPaths.length; i++) {
var clipPath = clipPaths[i];
var m;
if (clipPath.transform) {
m = clipPath.transform;
ctx.transform(
m[0], m[1],
m[2], m[3],
m[4], m[5]
);
}
var path = clipPath.path;
path.beginPath(ctx);
clipPath.buildPath(path, clipPath.shape);
ctx.clip();
// Transform back
if (clipPath.transform) {
m = clipPath.invTransform;
ctx.transform(
m[0], m[1],
m[2], m[3],
m[4], m[5]
);
}
}
}
function createRoot(width, height) {
var domRoot = document.createElement('div');
var domRootStyle = domRoot.style;
// domRoot.onselectstart = returnFalse; // 避免页面选中的尴尬
domRootStyle.position = 'relative';
domRootStyle.overflow = 'hidden';
domRootStyle.width = width + 'px';
domRootStyle.height = height + 'px';
return domRoot;
}
/**
* @alias module:zrender/Painter
* @constructor
* @param {HTMLElement} root 绘图容器
* @param {module:zrender/Storage} storage
* @param {Ojbect} opts
*/
var Painter = function (root, storage, opts) {
// In node environment using node-canvas
var singleCanvas = !root.nodeName // In node ?
|| root.nodeName.toUpperCase() === 'CANVAS';
opts = opts || {};
/**
* @type {number}
*/
this.dpr = opts.devicePixelRatio || config.devicePixelRatio;
/**
* @type {boolean}
* @private
*/
this._singleCanvas = singleCanvas;
/**
* 绘图容器
* @type {HTMLElement}
*/
this.root = root;
var rootStyle = root.style;
if (rootStyle) {
rootStyle['-webkit-tap-highlight-color'] = 'transparent';
rootStyle['-webkit-user-select'] =
rootStyle['user-select'] =
rootStyle['-webkit-touch-callout'] = 'none';
root.innerHTML = '';
}
/**
* @type {module:zrender/Storage}
*/
this.storage = storage;
/**
* @type {Array.}
* @private
*/
var zlevelList = this._zlevelList = [];
/**
* @type {Object.}
* @private
*/
var layers = this._layers = {};
/**
* @type {Object.}
* @type {private}
*/
this._layerConfig = {};
if (!singleCanvas) {
this._width = this._getWidth();
this._height = this._getHeight();
var domRoot = this._domRoot = createRoot(
this._width, this._height
);
root.appendChild(domRoot);
}
else {
// Use canvas width and height directly
var width = root.width;
var height = root.height;
this._width = width;
this._height = height;
// Create layer if only one given canvas
// Device pixel ratio is fixed to 1 because given canvas has its specified width and height
var mainLayer = new Layer(root, this, 1);
mainLayer.initContext();
// FIXME Use canvas width and height
// mainLayer.resize(width, height);
layers[0] = mainLayer;
zlevelList.push(0);
}
this.pathToImage = this._createPathToImage();
// Layers for progressive rendering
this._progressiveLayers = [];
/**
* @type {module:zrender/Layer}
* @private
*/
this._hoverlayer;
this._hoverElements = [];
};
Painter.prototype = {
constructor: Painter,
/**
* If painter use a single canvas
* @return {boolean}
*/
isSingleCanvas: function () {
return this._singleCanvas;
},
/**
* @return {HTMLDivElement}
*/
getViewportRoot: function () {
return this._singleCanvas ? this._layers[0].dom : this._domRoot;
},
/**
* 刷新
* @param {boolean} [paintAll=false] 强制绘制所有displayable
*/
refresh: function (paintAll) {
var list = this.storage.getDisplayList(true);
var zlevelList = this._zlevelList;
this._paintList(list, paintAll);
// Paint custum layers
for (var i = 0; i < zlevelList.length; i++) {
var z = zlevelList[i];
var layer = this._layers[z];
if (!layer.isBuildin && layer.refresh) {
layer.refresh();
}
}
this.refreshHover();
if (this._progressiveLayers.length) {
this._startProgessive();
}
return this;
},
addHover: function (el, hoverStyle) {
if (el.__hoverMir) {
return;
}
var elMirror = new el.constructor({
style: el.style,
shape: el.shape
});
elMirror.__from = el;
el.__hoverMir = elMirror;
elMirror.setStyle(hoverStyle);
this._hoverElements.push(elMirror);
},
removeHover: function (el) {
var elMirror = el.__hoverMir;
var hoverElements = this._hoverElements;
var idx = util.indexOf(hoverElements, elMirror);
if (idx >= 0) {
hoverElements.splice(idx, 1);
}
el.__hoverMir = null;
},
clearHover: function (el) {
var hoverElements = this._hoverElements;
for (var i = 0; i < hoverElements.length; i++) {
var from = hoverElements[i].__from;
if (from) {
from.__hoverMir = null;
}
}
hoverElements.length = 0;
},
refreshHover: function () {
var hoverElements = this._hoverElements;
var len = hoverElements.length;
var hoverLayer = this._hoverlayer;
hoverLayer && hoverLayer.clear();
if (!len) {
return;
}
// Use a extream large zlevel
// FIXME?
if (!hoverLayer) {
hoverLayer = this._hoverlayer = this.getLayer(1e5);
}
var scope = {};
hoverLayer.ctx.save();
for (var i = 0; i < len;) {
var el = hoverElements[i];
var originalEl = el.__from;
// Original el is removed
// PENDING
if (!(originalEl && originalEl.__zr)) {
hoverElements.splice(i, 1);
originalEl.__hoverMir = null;
len--;
continue;
}
i++;
// Use transform
// FIXME style and shape ?
el.transform = originalEl.transform;
el.invTransform = originalEl.invTransform;
el.__clipPaths = originalEl.__clipPaths;
// el.
this._doPaintEl(el, hoverLayer, true, scope);
}
hoverLayer.ctx.restore();
},
_startProgessive: function () {
var self = this;
if (!self._furtherProgressive) {
return;
}
// Use a token to stop progress steps triggered by
// previous zr.refresh calling.
var token = self._progressiveToken = +new Date();
self._progress++;
requestAnimationFrame(step);
function step() {
// In case refreshed or disposed
if (token === self._progressiveToken && self.storage) {
self._doPaintList(self.storage.getDisplayList());
if (self._furtherProgressive) {
self._progress++;
requestAnimationFrame(step);
}
else {
self._progressiveToken = -1;
}
}
}
},
_clearProgressive: function () {
this._progressiveToken = -1;
this._progress = 0;
util.each(this._progressiveLayers, function (layer) {
layer.__dirty && layer.clear();
});
},
_paintList: function (list, paintAll) {
if (paintAll == null) {
paintAll = false;
}
this._updateLayerStatus(list);
this._clearProgressive();
this.eachBuildinLayer(preProcessLayer);
this._doPaintList(list, paintAll);
this.eachBuildinLayer(postProcessLayer);
},
_doPaintList: function (list, paintAll) {
var currentLayer;
var currentZLevel;
var ctx;
// var invTransform = [];
var scope;
var progressiveLayerIdx = 0;
var currentProgressiveLayer;
var width = this._width;
var height = this._height;
var layerProgress;
var frame = this._progress;
function flushProgressiveLayer(layer) {
// Avoid layer not clear in next progressive frame
currentLayer.__dirty = true;
ctx.drawImage(layer.dom, 0, 0, width, height);
currentLayer.ctx.restore();
}
for (var i = 0, l = list.length; i < l; i++) {
var el = list[i];
var elZLevel = this._singleCanvas ? 0 : el.zlevel;
// Change draw layer
if (currentZLevel !== elZLevel) {
if (ctx) {
ctx.restore();
}
// Reset scope
scope = { prevElClipPaths: null };
// Only 0 zlevel if only has one canvas
currentZLevel = elZLevel;
currentLayer = this.getLayer(currentZLevel);
if (!currentLayer.isBuildin) {
log(
'ZLevel ' + currentZLevel
+ ' has been used by unkown layer ' + currentLayer.id
);
}
ctx = currentLayer.ctx;
ctx.save();
// Reset the count
currentLayer.__unusedCount = 0;
if (currentLayer.__dirty || paintAll) {
currentLayer.clear();
}
}
var elFrame = el.__frame;
if (!(currentLayer.__dirty || paintAll)) {
continue;
}
if (elFrame >= 0) {
// Progressive layer changed
if (!currentProgressiveLayer) {
currentProgressiveLayer = this._progressiveLayers[
Math.min(progressiveLayerIdx++, MAX_PROGRESSIVE_LAYER_NUMBER - 1)
];
currentProgressiveLayer.ctx.save();
if (currentProgressiveLayer
&& (currentProgressiveLayer.__progress > currentProgressiveLayer.__maxProgress)
) {
// flushProgressiveLayer(currentProgressiveLayer);
// Quick jump all progressive elements
// All progressive element are not dirty, jump over and flush directly
i = currentProgressiveLayer.__nextIdxNotProg - 1;
// currentProgressiveLayer = null;
continue;
}
layerProgress = currentProgressiveLayer.__progress;
if (!currentProgressiveLayer.__dirty) {
// Keep rendering
frame = layerProgress;
}
currentProgressiveLayer.__progress = frame + 1;
}
// console.log(elFrame, frame);
if (elFrame === frame) {
this._doPaintEl(el, currentProgressiveLayer, true, scope);
}
}
else {
if (currentProgressiveLayer) {
flushProgressiveLayer(currentProgressiveLayer);
currentProgressiveLayer = null;
}
this._doPaintEl(el, currentLayer, paintAll, scope);
}
el.__dirty = false;
}
if (currentProgressiveLayer) {
flushProgressiveLayer(currentProgressiveLayer);
}
// Restore the lastLayer ctx
ctx && ctx.restore();
// If still has clipping state
// if (scope.prevElClipPaths) {
// ctx.restore();
// }
this._furtherProgressive = false;
util.each(this._progressiveLayers, function (layer) {
if (layer.__maxProgress >= layer.__progress) {
this._furtherProgressive = true;
}
}, this);
},
_doPaintEl: function (el, currentLayer, forcePaint, scope) {
var ctx = currentLayer.ctx;
if (
(currentLayer.__dirty || forcePaint)
// Ignore invisible element
&& !el.invisible
// Ignore transparent element
&& el.style.opacity !== 0
// Ignore scale 0 element, in some environment like node-canvas
// Draw a scale 0 element can cause all following draw wrong
&& el.scale[0] && el.scale[1]
// Ignore culled element
&& !(el.culling && isDisplayableCulled(el, this._width, this._height))
) {
var clipPaths = el.__clipPaths;
// Optimize when clipping on group with several elements
if (scope.prevClipLayer !== currentLayer
|| isClipPathChanged(clipPaths, scope.prevElClipPaths)
) {
// If has previous clipping state, restore from it
if (scope.prevElClipPaths) {
scope.prevClipLayer.ctx.restore();
scope.prevClipLayer = scope.prevElClipPaths = null;
// Reset prevEl since context has been restored
scope.prevEl = null;
}
// New clipping state
if (clipPaths) {
ctx.save();
doClip(clipPaths, ctx);
scope.prevClipLayer = currentLayer;
scope.prevElClipPaths = clipPaths;
}
}
el.beforeBrush && el.beforeBrush(ctx);
el.brush(ctx, scope.prevEl || null);
scope.prevEl = el;
el.afterBrush && el.afterBrush(ctx);
}
},
/**
* 获取 zlevel 所在层,如果不存在则会创建一个新的层
* @param {number} zlevel
* @return {module:zrender/Layer}
*/
getLayer: function (zlevel) {
if (this._singleCanvas) {
return this._layers[0];
}
var layer = this._layers[zlevel];
if (!layer) {
// Create a new layer
layer = new Layer('zr_' + zlevel, this, this.dpr);
layer.isBuildin = true;
if (this._layerConfig[zlevel]) {
util.merge(layer, this._layerConfig[zlevel], true);
}
this.insertLayer(zlevel, layer);
// Context is created after dom inserted to document
// Or excanvas will get 0px clientWidth and clientHeight
layer.initContext();
}
return layer;
},
insertLayer: function (zlevel, layer) {
var layersMap = this._layers;
var zlevelList = this._zlevelList;
var len = zlevelList.length;
var prevLayer = null;
var i = -1;
var domRoot = this._domRoot;
if (layersMap[zlevel]) {
log('ZLevel ' + zlevel + ' has been used already');
return;
}
// Check if is a valid layer
if (!isLayerValid(layer)) {
log('Layer of zlevel ' + zlevel + ' is not valid');
return;
}
if (len > 0 && zlevel > zlevelList[0]) {
for (i = 0; i < len - 1; i++) {
if (
zlevelList[i] < zlevel
&& zlevelList[i + 1] > zlevel
) {
break;
}
}
prevLayer = layersMap[zlevelList[i]];
}
zlevelList.splice(i + 1, 0, zlevel);
if (prevLayer) {
var prevDom = prevLayer.dom;
if (prevDom.nextSibling) {
domRoot.insertBefore(
layer.dom,
prevDom.nextSibling
);
}
else {
domRoot.appendChild(layer.dom);
}
}
else {
if (domRoot.firstChild) {
domRoot.insertBefore(layer.dom, domRoot.firstChild);
}
else {
domRoot.appendChild(layer.dom);
}
}
layersMap[zlevel] = layer;
},
// Iterate each layer
eachLayer: function (cb, context) {
var zlevelList = this._zlevelList;
var z;
var i;
for (i = 0; i < zlevelList.length; i++) {
z = zlevelList[i];
cb.call(context, this._layers[z], z);
}
},
// Iterate each buildin layer
eachBuildinLayer: function (cb, context) {
var zlevelList = this._zlevelList;
var layer;
var z;
var i;
for (i = 0; i < zlevelList.length; i++) {
z = zlevelList[i];
layer = this._layers[z];
if (layer.isBuildin) {
cb.call(context, layer, z);
}
}
},
// Iterate each other layer except buildin layer
eachOtherLayer: function (cb, context) {
var zlevelList = this._zlevelList;
var layer;
var z;
var i;
for (i = 0; i < zlevelList.length; i++) {
z = zlevelList[i];
layer = this._layers[z];
if (! layer.isBuildin) {
cb.call(context, layer, z);
}
}
},
/**
* 获取所有已创建的层
* @param {Array.} [prevLayer]
*/
getLayers: function () {
return this._layers;
},
_updateLayerStatus: function (list) {
var layers = this._layers;
var progressiveLayers = this._progressiveLayers;
var elCountsLastFrame = {};
var progressiveElCountsLastFrame = {};
this.eachBuildinLayer(function (layer, z) {
elCountsLastFrame[z] = layer.elCount;
layer.elCount = 0;
layer.__dirty = false;
});
util.each(progressiveLayers, function (layer, idx) {
progressiveElCountsLastFrame[idx] = layer.elCount;
layer.elCount = 0;
layer.__dirty = false;
});
var progressiveLayerCount = 0;
var currentProgressiveLayer;
var lastProgressiveKey;
var frameCount = 0;
for (var i = 0, l = list.length; i < l; i++) {
var el = list[i];
var zlevel = this._singleCanvas ? 0 : el.zlevel;
var layer = layers[zlevel];
var elProgress = el.progressive;
if (layer) {
layer.elCount++;
layer.__dirty = layer.__dirty || el.__dirty;
}
// Update progressive
if (elProgress >= 0) {
// Fix wrong progressive sequence problem.
if (lastProgressiveKey !== elProgress) {
lastProgressiveKey = elProgress;
frameCount++;
}
var elFrame = el.__frame = frameCount - 1;
if (!currentProgressiveLayer) {
var idx = Math.min(progressiveLayerCount, MAX_PROGRESSIVE_LAYER_NUMBER - 1);
currentProgressiveLayer = progressiveLayers[idx];
if (!currentProgressiveLayer) {
currentProgressiveLayer = progressiveLayers[idx] = new Layer(
'progressive', this, this.dpr
);
currentProgressiveLayer.initContext();
}
currentProgressiveLayer.__maxProgress = 0;
}
currentProgressiveLayer.__dirty = currentProgressiveLayer.__dirty || el.__dirty;
currentProgressiveLayer.elCount++;
currentProgressiveLayer.__maxProgress = Math.max(
currentProgressiveLayer.__maxProgress, elFrame
);
}
else {
el.__frame = -1;
if (currentProgressiveLayer) {
currentProgressiveLayer.__nextIdxNotProg = i;
progressiveLayerCount++;
currentProgressiveLayer = null;
}
}
}
if (currentProgressiveLayer) {
progressiveLayerCount++;
currentProgressiveLayer.__nextIdxNotProg = i;
}
// 层中的元素数量有发生变化
this.eachBuildinLayer(function (layer, z) {
if (elCountsLastFrame[z] !== layer.elCount) {
layer.__dirty = true;
}
});
progressiveLayers.length = Math.min(progressiveLayerCount, MAX_PROGRESSIVE_LAYER_NUMBER);
util.each(progressiveLayers, function (layer, idx) {
if (progressiveElCountsLastFrame[idx] !== layer.elCount) {
el.__dirty = true;
}
if (layer.__dirty) {
layer.__progress = 0;
}
});
},
/**
* 清除hover层外所有内容
*/
clear: function () {
this.eachBuildinLayer(this._clearLayer);
return this;
},
_clearLayer: function (layer) {
layer.clear();
},
/**
* 修改指定zlevel的绘制参数
*
* @param {string} zlevel
* @param {Object} config 配置对象
* @param {string} [config.clearColor=0] 每次清空画布的颜色
* @param {string} [config.motionBlur=false] 是否开启动态模糊
* @param {number} [config.lastFrameAlpha=0.7]
* 在开启动态模糊的时候使用,与上一帧混合的alpha值,值越大尾迹越明显
*/
configLayer: function (zlevel, config) {
if (config) {
var layerConfig = this._layerConfig;
if (!layerConfig[zlevel]) {
layerConfig[zlevel] = config;
}
else {
util.merge(layerConfig[zlevel], config, true);
}
var layer = this._layers[zlevel];
if (layer) {
util.merge(layer, layerConfig[zlevel], true);
}
}
},
/**
* 删除指定层
* @param {number} zlevel 层所在的zlevel
*/
delLayer: function (zlevel) {
var layers = this._layers;
var zlevelList = this._zlevelList;
var layer = layers[zlevel];
if (!layer) {
return;
}
layer.dom.parentNode.removeChild(layer.dom);
delete layers[zlevel];
zlevelList.splice(util.indexOf(zlevelList, zlevel), 1);
},
/**
* 区域大小变化后重绘
*/
resize: function (width, height) {
var domRoot = this._domRoot;
// FIXME Why ?
domRoot.style.display = 'none';
width = width || this._getWidth();
height = height || this._getHeight();
domRoot.style.display = '';
// 优化没有实际改变的resize
if (this._width != width || height != this._height) {
domRoot.style.width = width + 'px';
domRoot.style.height = height + 'px';
for (var id in this._layers) {
this._layers[id].resize(width, height);
}
this.refresh(true);
}
this._width = width;
this._height = height;
return this;
},
/**
* 清除单独的一个层
* @param {number} zlevel
*/
clearLayer: function (zlevel) {
var layer = this._layers[zlevel];
if (layer) {
layer.clear();
}
},
/**
* 释放
*/
dispose: function () {
this.root.innerHTML = '';
this.root =
this.storage =
this._domRoot =
this._layers = null;
},
/**
* Get canvas which has all thing rendered
* @param {Object} opts
* @param {string} [opts.backgroundColor]
*/
getRenderedCanvas: function (opts) {
opts = opts || {};
if (this._singleCanvas) {
return this._layers[0].dom;
}
var imageLayer = new Layer('image', this, opts.pixelRatio || this.dpr);
imageLayer.initContext();
imageLayer.clearColor = opts.backgroundColor;
imageLayer.clear();
var displayList = this.storage.getDisplayList(true);
var scope = {};
for (var i = 0; i < displayList.length; i++) {
var el = displayList[i];
this._doPaintEl(el, imageLayer, true, scope);
}
return imageLayer.dom;
},
/**
* 获取绘图区域宽度
*/
getWidth: function () {
return this._width;
},
/**
* 获取绘图区域高度
*/
getHeight: function () {
return this._height;
},
_getWidth: function () {
var root = this.root;
var stl = document.defaultView.getComputedStyle(root);
// FIXME Better way to get the width and height when element has not been append to the document
return ((root.clientWidth || parseInt10(stl.width) || parseInt10(root.style.width))
- (parseInt10(stl.paddingLeft) || 0)
- (parseInt10(stl.paddingRight) || 0)) | 0;
},
_getHeight: function () {
var root = this.root;
var stl = document.defaultView.getComputedStyle(root);
return ((root.clientHeight || parseInt10(stl.height) || parseInt10(root.style.height))
- (parseInt10(stl.paddingTop) || 0)
- (parseInt10(stl.paddingBottom) || 0)) | 0;
},
_pathToImage: function (id, path, width, height, dpr) {
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
canvas.width = width * dpr;
canvas.height = height * dpr;
ctx.clearRect(0, 0, width * dpr, height * dpr);
var pathTransform = {
position: path.position,
rotation: path.rotation,
scale: path.scale
};
path.position = [0, 0, 0];
path.rotation = 0;
path.scale = [1, 1];
if (path) {
path.brush(ctx);
}
var ImageShape = require('./graphic/Image');
var imgShape = new ImageShape({
id: id,
style: {
x: 0,
y: 0,
image: canvas
}
});
if (pathTransform.position != null) {
imgShape.position = path.position = pathTransform.position;
}
if (pathTransform.rotation != null) {
imgShape.rotation = path.rotation = pathTransform.rotation;
}
if (pathTransform.scale != null) {
imgShape.scale = path.scale = pathTransform.scale;
}
return imgShape;
},
_createPathToImage: function () {
var me = this;
return function (id, e, width, height) {
return me._pathToImage(
id, e, width, height, me.dpr
);
};
}
};
return Painter;
});