前端日志、表格改为canvas绘制。提高性能

This commit is contained in:
mxd 2019-09-07 19:56:09 +08:00
parent 13f6a2adca
commit 34009d77b3
4 changed files with 441 additions and 112 deletions

View File

@ -91,14 +91,10 @@ html,body{
}
.test-window-container .output-container{
height: 320px;
overflow: auto;
/* height: 320px; */
}
.test-window-container .log-container{
max-height: 100px;
overflow: auto;
border: 1px solid #ccc;
padding: 0 10px;
}
.test-window-container .log-container textarea{
width : 100%;

View File

@ -15,6 +15,7 @@
<script type="text/javascript" src="js/mxgraph/mxgraph.min.js" ></script>
<script type="text/javascript" src="js/common.js" ></script>
<script type="text/javascript" src="js/spider-editor.js" ></script>
<script type="text/javascript" src="js/canvas-viewer.js" ></script>
<script type="text/javascript" src="js/codemirror/codemirror.js" ></script>
<script type="text/javascript" src="js/codemirror/spiderflow.js" ></script>
<script type="text/javascript" src="js/codemirror/placeholder.js" ></script>

View File

@ -0,0 +1,317 @@
window.requestAnimFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback,element){
window.setTimeout(callback, 1000 / 60);
}
window.cancelAnimationFrame=window.cancelAnimationFrame || window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame || window.msCancelAnimationFrame || window.oCancelAnimationFrame || function( id ){
window.clearTimeout( id );
}
var canvas = document.getElementById('logviewer');
function CanvasText(options){
options = options || {};
this.maxWidth = options.maxWidth || 2147483648;
this.color = options.color;
this.text = options.text.toString();
this.click = options.click;
this.startX = 0;
this.endX = 0;
}
function CanvasViewer(options){
options = options || {};
this.canvas = options.element;
this.context = this.canvas.getContext('2d');
this.style = options.style || {};
this.context.font = this.style.font || 'bold 14px Consolas';
this.context.textBaseline = this.style.textBaseLine || 'middle';
this.lines = [];
this.lineHeight = 24;
this.maxRows = Math.ceil(this.canvas.height / this.lineHeight);
this.scrollHeight = this.canvas.height;
this.scrollTop = 0;
var _this = this;
this.mouseEvent = 0;
this.mouseDownX = 0;
this.mouseDownY = 0;
this.startIndex = 0;
this.startX = 5;
this.mouseX = 0;
this.mouseY = 0;
this.colsOffsetX = [0];
this.maxWidth = this.canvas.width - 8;
this.onClick = options.onClick || function(){};
this.grid = options.grid;
this.header = options.header;
this.canvas.onmousemove = function(e){
var x = e.offsetX;
var y = e.offsetY;
_this.mouseX = x;
_this.mouseY = y;
var _hover = false;
if(x < _this.canvas.width - 8 && y < _this.canvas.height - 8){
var row = _this.startIndex + parseInt(y / _this.lineHeight) + (y % _this.lineHeight == 0 ? 0 : 1) - 1;
var texts = _this.lines[row];
if(texts){
for(var i =0,len = texts.length;i<len;i++){
var text = texts[i];
if(text.click&&text.startX < x && text.endX > x){
_hover = true;
break;
}
}
}
}
if(_hover){
_this.canvas.style.cursor = 'pointer';
}else{
_this.canvas.style.cursor = 'default';
}
//鼠标按下且有纵向滚动条
if(_this.mouseEvent == 1 && _this.hasScroll){
_this.scrollTo(Math.max(Math.min(Math.floor((y / (_this.canvas.height - 8)) * _this.lines.length - _this.maxRows / 2),_this.lines.length - _this.maxRows),0));
}
//鼠标按下且有横向滚动条
if(_this.mouseEvent == 2 && _this.hasXScroll){
var delta = e.offsetX - _this.mouseDownX;
_this.startX = Math.min(-(_this.maxWidth-_this.canvas.width) * (x / (_this.canvas.width - 8)),0) + 5;
}
}
this.canvas.onmousewheel = function(e){
if(e.wheelDelta > 0){ //向上滚动
_this.scrollTo(Math.max(_this.startIndex - 2,0));
}else{
_this.scrollTo(Math.max(Math.min(_this.startIndex + 1,_this.lines.length - _this.maxRows),0));
}
}
this.canvas.onmousedown = function(e){
if(e.offsetX > _this.canvas.width - 8 && e.offsetY < _this.canvas.height - 8){
_this.mouseEvent = 1;
_this.mouseDownX = e.offsetX;
_this.mouseDownY = e.offsetY;
}
if(e.offsetX < _this.canvas.width - 8 && e.offsetY > _this.canvas.height - 8){
_this.mouseEvent = 2;
_this.mouseDownX = e.offsetX;
_this.mouseDownY = e.offsetY;
}
}
this.canvas.onmouseup = this.canvas.onmouseout = function(){
_this.mouseEvent = 0;
_this.mouseDownX = 0;
_this.mouseDownY = 0;
}
this.canvas.onclick = function(e){
var x = e.offsetX;
var y = e.offsetY;
if(x < _this.canvas.width - 8 && y < _this.canvas.height - 8){
var row = _this.startIndex + parseInt(y / _this.lineHeight) + (y % _this.lineHeight == 0 ? 0 : 1) - 1;
var _hover = false;
var texts = _this.lines[row];
if(texts){
for(var i =0,len = texts.length;i<len;i++){
var text = texts[i];
if(text.click&&text.startX < x && text.endX > x){
_this.onClick(text);
break;
}
}
}
}
}
var animate = function(){
_this.animateIndex = requestAnimFrame(animate);
_this.redraw();
}
animate();
}
CanvasViewer.prototype.destory = function(){
cancelAnimationFrame(animateIndex);
this.texts = null;
}
CanvasViewer.prototype.append = function(texts){
var width = texts.length * 10 - 10;
for(var i =0,len = texts.length;i<len;i++){
var text = texts[i];
var w = 0;
if(text.maxWidth > 0){
w = this._drawLongText(text.text,0,0,text.maxWidth,true);
}else{
w = this.context.measureText(content).width;
}
this.colsOffsetX[i] = Math.max(this.colsOffsetX[i] || 0,w);
width += w;
}
this.maxWidth = Math.max(this.maxWidth,width);
this.lines.push(texts);
}
CanvasViewer.prototype.resize = function(){
var prevMaxRows = this.maxRows;
this.maxRows = Math.ceil(this.canvas.height / this.lineHeight);
this.context = this.canvas.getContext('2d');
this.context.font = 'bold 14px Consolas';
this.context.textBaseline = 'middle';
this.scrollTo(this.startIndex + (prevMaxRows - this.maxRows));
this.redraw();
}
CanvasViewer.prototype._drawScroll = function(){
var surplus = this.lines.length - this.maxRows;
this.hasScroll = surplus > 0;
this.context.clearRect(x,canvasHeight,8,8);
if(this.hasScroll){
var canvasHeight = this.canvas.height - 8;
this.scrollHeight = canvasHeight + this.lineHeight * surplus;
this.slideHeight = Math.max(canvasHeight * (canvasHeight / this.scrollHeight),10);
this.scrollTop = Math.min(this.startIndex / this.lines.length * canvasHeight,canvasHeight - this.slideHeight);
this.context.save();
this.context.beginPath();
var x = this.canvas.width - 8;
var y = this.scrollTop;
var r = 4;
var width = 8;
var height = this.slideHeight;
this.context.fillStyle = '#f1f1f1';
this.context.fillRect(x,0,8,canvasHeight);
this.context.moveTo(x + r, y);
this.context.arcTo(x + width, y, x + width, y + r, r);
this.context.arcTo(x + width, y + height, x + width - r, y + height, r);
this.context.arcTo(x, y + height, x, y + height - r, r);
this.context.arcTo(x, y, x + r, y, r);
if(this.mouseEvent == 1){
this.context.fillStyle = '#787878';
}else if(this.mouseX > x && this.mouseY > y &&this.mouseY < y + height){
this.context.fillStyle = '#a8a8a8';
}else{
this.context.fillStyle = '#c1c1c1';
}
this.context.fill();
this.context.restore();
}
//
this.hasXScroll = this.maxWidth > this.canvas.width - 8;
if(this.hasXScroll){
var canvasWidth = this.canvas.width - 8;
this.scrollWidth = this.maxWidth;
this.slideWidth = Math.max(canvasWidth * (canvasWidth / this.scrollWidth),10);
this.scrollLeft = Math.min(-this.startX / this.maxWidth * canvasWidth,canvasWidth - this.slideWidth);
this.context.save();
this.context.beginPath();
var x = this.scrollLeft;
var y = this.canvas.height - 8;
var r = 4;
var width = this.slideWidth;
var height = 8;
this.context.fillStyle = '#f1f1f1';
this.context.fillRect(0,this.canvas.height - 8,canvasWidth,8);
this.context.moveTo(x + r, y);
this.context.arcTo(x + width, y, x + width, y + r, r);
this.context.arcTo(x + width, y + height, x + width - r, y + height, r);
this.context.arcTo(x, y + height, x, y + height - r, r);
this.context.arcTo(x, y, x + r, y, r);
if(this.mouseEvent == 2){
this.context.fillStyle = '#787878';
}else if(this.mouseX > x && this.mouseY > y &&this.mouseX < x + width){
this.context.fillStyle = '#a8a8a8';
}else{
this.context.fillStyle = '#c1c1c1';
}
this.context.fill();
this.context.restore();
}
}
CanvasViewer.prototype.scrollTo = function(index){
if(index < 0){
index = this.lines.length - 1;
}
this.startIndex = Math.max(Math.min(Math.max(index,0),this.lines.length - this.maxRows),0);
if(this.startIndex > 0){
this.startIndex = this.startIndex + 1;
}
}
CanvasViewer.prototype.redraw = function(){
var lines = this.lines.slice(this.startIndex,this.startIndex + this.maxRows);
this.context.clearRect(0,0,this.canvas.width,this.canvas.height);
this.context.lineWidth = 1;
this.context.strokeStyle = '#e6e6e6';
this.context.font = this.style.font || 'bold 14px Consolas';
this.context.textBaseline = this.style.textBaseLine || 'middle';
var cols = [0];
var maxY = 0;
var maxX = 0;
for(var i=0,l =lines.length;i<l;i++){
var texts = i==0&&this.grid&&this.header ? this.lines[0]: lines[i];
var x = this.startX;
var y = i * this.lineHeight + this.lineHeight / 2;
maxY = y + this.lineHeight / 2;
for(var j =0,t = texts.length;j < t;j++){
var text = texts[j];
var content = text.text;
this.context.fillStyle = text.color || 'black';
var width = this.context.measureText(content).width;
if(text.maxWidth > 0){
width = this._drawLongText(content,x,y,text.maxWidth);
}else{
this.context.fillText(content,x,y);
}
text.startX = x;
text.endX = x + width;
x = x + ((this.grid ? this.colsOffsetX[j] : 0) || width) + 10;
maxX = x - 5;
if(this.grid){
cols[j + 1] = Math.max(cols[j + 1] || 0,maxX);
}
}
}
if(this.grid && lines.length > 0){
for(var i=0;i<=lines.length;i++){
if(this.grid){
this.context.save();
this.context.beginPath();
this.context.moveTo(2.5,i * 24 + 0.5);
this.context.lineTo(maxX + 0.5, i * 24 + 0.5);
this.context.stroke();
this.context.restore();
}
}
for(var i=0;i < cols.length;i++){
var x = cols[i];
this.context.save();
this.context.beginPath();
this.context.moveTo(x + 0.5,0.5);
this.context.lineTo(x + 0.5, maxY);
this.context.stroke();
this.context.restore();
}
}
this._drawScroll();
}
CanvasViewer.prototype._drawLongText = function(text,x,y,maxWidth,calcWidth){
var length = text.length;
var index = 0;
var width = 0;
while(index < length){
var str = text.substr(index,1);
var w = this.context.measureText(str).width;
width+= w;
if(width > maxWidth){
width-=w;
break;
}
if(calcWidth === undefined){
this.context.fillText(str,x + width - w,y);
}
index++;
}
if(index < length){
var w = this.context.measureText('...').width;
width+=w;
if(calcWidth === undefined){
this.context.fillText('...',x + width - w,y);
}
}
return width;
}

View File

@ -559,9 +559,11 @@ function bindToolbarClickAction(editor){
// editor.setXML($(".xml-container textarea").val());
// editor.onSelectedCell();
}).on('click','.btn-test',function(){
var LogViewer;
var tableMap = {};
layui.layer.open({
id : 'test-window',
content : '<div class="test-window-container"><div class="output-container"></div><div class="log-container"></div></div>',
content : '<div class="test-window-container"><div class="output-container"></div><canvas class="log-container" width="960" height="100"></canvas></div>',
area : ["1000px","600px"],
shade : 0,
title : '测试窗口',
@ -571,17 +573,19 @@ function bindToolbarClickAction(editor){
var $log = $(".test-window-container .log-container");
if($output.is(":hidden")){
$output.show();
$output.css({
height : $log.is(":hidden") ? '420px' : '320px'
})
$log.css({
maxHeight : '100px'
})
$output.find("canvas").css('height', $log.is(":hidden") ? 460 : 320)
$log.attr('height',100)
LogViewer.resize();
for(var tableId in tableMap){
tableMap[tableId].resize();
}
}else{
$output.hide();
$log.css({
maxHeight : '420px'
})
$log.attr('height',460);
LogViewer.resize();
for(var tableId in tableMap){
tableMap[tableId].resize();
}
}
return false;
},
@ -590,25 +594,36 @@ function bindToolbarClickAction(editor){
var $log = $(".test-window-container .log-container");
if($log.is(":hidden")){
$log.show();
$log.css({
maxHeight : $output.is(":hidden") ? '420px' : '100px'
})
$output.css({
height : '320px'
})
$log.attr('height',$output.is(":hidden") ? 460 : 100)
$output.find("canvas").attr('height',320);
LogViewer.resize();
for(var tableId in tableMap){
tableMap[tableId].resize();
}
}else{
$log.hide();
$output.css({
height : '420px'
})
$output.find("canvas").attr('height',460);
LogViewer.resize();
for(var tableId in tableMap){
tableMap[tableId].resize();
}
}
var logElement = $(".test-window-container .log-container")[0];
logElement.scrollTop = logElement.scrollHeight;
return false;
},
success : function(){
var tableMap = {};
var logElement = $(".test-window-container .log-container")[0];
var colors = {
'array' : '#2a00ff',
'object' : '#2a00ff',
'boolean' : '#600100',
'number' : '#000E59'
}
LogViewer = new CanvasViewer({
element : logElement,
onClick : function(e){
onCanvasViewerClick(e,'日志');
}
})
var socket = createWebSocket({
onopen : function(){
socket.send(JSON.stringify({
@ -624,55 +639,59 @@ function bindToolbarClickAction(editor){
var tableId = 'output-' + message.nodeId;
var $table = $('#' + tableId);
if($table.length == 0){
$table = $('<table/>').appendTo($(".test-window-container .output-container"));
$table.attr('id',tableId).attr("class","layui-table").attr("size","mini");
var cols = [];
for(var i =0,len = message.outputNames.length;i<len;i++){
cols.push({
field : message.outputNames[i],
title : message.outputNames[i]
})
}
tableMap[tableId] = {
cols : [cols],
data : []
};
}
var row = {};
for(var i =0,len = message.outputNames.length;i<len;i++){
row[message.outputNames[i]] = message.values[i];
}
tableMap[tableId].data.unshift(row);
if(tableMap[tableId].instance){
$('[lay-id="'+tableId+'"] .layui-laypage-btn').trigger('click');
}else{
tableMap[tableId].instance = layui.table.render({
elem : '#' + tableId,
cols : tableMap[tableId].cols,
data : tableMap[tableId].data,
page : true,
limit : 5,
limits : [5]
tableMap[tableId] = {};
$table = $('<canvas width="960" height="320"/>').appendTo($(".test-window-container .output-container"));
$table.attr('id',tableId);
tableMap[tableId] = new CanvasViewer({
element : document.getElementById(tableId),
grid : true,
header : true,
style : {
font : '12px Arial'
},
onClick : function(e){
onCanvasViewerClick(e,'表格');
}
})
var cols = [];
var texts = [];
for(var i =0,len = message.outputNames.length;i<len;i++){
texts.push(new CanvasText({
text : message.outputNames[i],
maxWidth : 200,
click : true
}));
}
tableMap[tableId].append(texts);
}
var texts = [];
for(var i =0,len = message.outputNames.length;i<len;i++){
texts.push(new CanvasText({
text : message.values[i],
maxWidth : 200,
click : true
}));
}
tableMap[tableId].append(texts);
tableMap[tableId].scrollTo(-1);
}else if(eventType == 'log'){
var fragment = document.createDocumentFragment();
var div = document.createElement('div');
div.className = 'test-log log-' + message.level;
var levelElement = document.createElement('span');
levelElement.className = 'level';
levelElement.innerHTML = message.level;
div.appendChild(levelElement);
var timestampElement = document.createElement('span');
timestampElement.className = 'timestamp';
timestampElement.innerHTML = event.timestamp;
div.appendChild(timestampElement);
var messageElement = document.createElement('span');
messageElement.className = 'message';
var msg = message.message;
if(message.variables){
for(var i=0,len=message.variables.length;i<len;i++){
var object = message.variables[i];
var texts = [];
texts.push(new CanvasText({
text : message.level
}));
texts.push(new CanvasText({
text : event.timestamp
}));
var temp = message.message.split("{}");
message.variables = message.variables || [];
for(var i=0,len=temp.length;i<len;i++){
if(temp[i]!=''){
texts.push(new CanvasText({
text : temp[i]
}))
}
var object = message.variables[i];
if(object != undefined){
var variableType = '';
var displayText = object;
if(Array.isArray(object)){
@ -682,22 +701,18 @@ function bindToolbarClickAction(editor){
variableType = typeof object;
if(variableType == 'object'){
displayText = JSON.stringify(displayText);
}else{
var temp = document.createElement('div');
(temp.textContent != null) ? (temp.textContent = displayText) : (temp.innerText = displayText);
displayText = temp.innerHTML;
temp = null;
}
}
msg = msg.replace('{}','</span><span class="variable variable-'+variableType+'">' + displayText + '</span><span>')
texts.push(new CanvasText({
text : displayText,
maxWidth : 230,
color : colors[variableType] || '#025900',
click : true
}))
}
}
messageElement.innerHTML = msg;
div.appendChild(messageElement);
fragment.appendChild(div);
logElement.appendChild(fragment);
logElement.scrollTop = logElement.scrollHeight;
LogViewer.append(texts);
LogViewer.scrollTo(-1);
}
}
});
@ -708,34 +723,34 @@ function bindToolbarClickAction(editor){
}).on('click','.btn-save',function(){
Save();
})
$('body').on('click','.log-container .variable',function(){
var msg = $(this).html();
var json;
try{
json = JSON.parse(msg);
if(!(Array.isArray(json) || typeof json == 'object')){
json = null;
}
}catch(e){
}
function onCanvasViewerClick(e,source){
var msg = e.text;
var json;
try{
json = JSON.parse(msg);
if(!(Array.isArray(json) || typeof json == 'object')){
json = null;
}
layer.open({
type : 1,
title : '日志内容',
content: '<div class="message-content" style="padding:10px;'+(json ? '':'font-weight:bold;')+'">'+(json ? '' : msg.replace(/\n/g,'<br>'))+'</div>',
shade : 0,
area : json ? ['700px','500px'] : 'auto',
maxmin : true,
maxWidth : json ? undefined : 700,
maxHeight : json ? undefined : 500,
success : function(dom,index){
var $dom = $(dom).find(".message-content");
if(json){
jsonTree.create(json,$dom[0]);
}
}
});
});
}catch(e){
}
layer.open({
type : 1,
title : source +'内容',
content: '<div class="message-content" style="padding:10px;'+(json ? '':'font-weight:bold;')+'">'+(json ? '' : msg.replace(/\n/g,'<br>'))+'</div>',
shade : 0,
area : json ? ['700px','500px'] : 'auto',
maxmin : true,
maxWidth : json ? undefined : 700,
maxHeight : json ? undefined : 500,
success : function(dom,index){
var $dom = $(dom).find(".message-content");
if(json){
jsonTree.create(json,$dom[0]);
}
}
});
}
function createWebSocket(options){
options = options || {};