Рисование на холсте происходит очень медленно

#javascript #html #canvas #html5-canvas

#javascript #HTML #холст #html5-canvas

Вопрос:

Я хочу отобразить масштаб с разметкой, который работает нормально. Кроме того, я также хочу отобразить местоположение мыши в масштабе с красным индикатором.

Итак, я рисую холст при запуске приложения, а затем перерисовываю весь холст при изменении местоположения мыши.

Я новичок в canvas и не понимаю, что не так в моем коде. Я пытался решить эту проблему, но безуспешно.

Проблема может быть в этой функции,

  function drawBlackMarkers(y, coordinateMeasurment){
    const markHightY = scaleTextPadding.initial;
    ctxLeft.moveTo(coordinateMeasurment, y   markHightY);
    ctxLeft.lineTo(completeMarkHight, y   markHightY);
  }
  

У меня большой цикл for, что означает выполнение стольких итераций, и в этом цикле я вызываю функцию drawBlackMarkers столько раз, как показано ниже.

 function setMarkers(initialValY, rangeValY, coordinateMeasurmentr, divisableVal,
    scaleCountStartValueOfY, scaleCountRangeValueOfY) {
    let count = 0;
    // re-modifying scale staring and ending values based on zoom factor
    const scaleInceremnt = scaleIncementValue;
    for (let y = (initialValY), scaleCountY = scaleCountStartValueOfY;
      y <= (rangeValY) amp;amp; scaleCountY <= scaleCountRangeValueOfY;
      y  = scaleInceremnt, scaleCountY  = incrementFactor) {


      switch (count) {
        case displayScale.starting:
          coordinateMeasurment = marktype.bigMark; count  ;
          const scaleValY = scaleCountY - divisableVal;

          ctxLeft.strokeStyle = colors.black;

          ctxLeft.font = scaleNumberFont;
          const size = ctxLeft.measureText(scaleValY.toString());
          ctxLeft.save();
          const textX = coordinateMeasurment   ((size.width) / 2);
          const textY = y - scaleTextPadding.alignment;
          ctxLeft.translate(textX, textY);
          ctxLeft.rotate(-Math.PI / 2);
          ctxLeft.translate(-textX, -textY);
          ctxLeft.fillText(scaleValY.toString(), coordinateMeasurment, y - scaleTextPadding.complete);
          ctxLeft.restore();
          break;
        case displayScale.middle:
          coordinateMeasurment = marktype.middleMark; count  ;
          break;
        case displayScale.end:
          coordinateMeasurment = marktype.smallMark; count = 0;
          break;
        default:
          coordinateMeasurment = marktype.smallMark; count  ;
          break;
      }

      // to draw scale lines on canvas
  // drawBlackMarkers(y, coordinateMeasurment);      
    }
  }
  

Пожалуйста, проверьте это:http://jsfiddle.net/3v5nt7fe/1 /

Проблема в том, что если я прокомментирую вызов функции drawBlackMarkers, обновление координат мыши происходит очень быстро, но если я раскомментирую, обновление местоположения займет слишком много времени.

Мне действительно нужна помощь для решения этой проблемы.

Комментарии:

1. Чтобы получить максимальную помощь, вам следует добавить функцию, в которой возникла проблема drawBlackMarkers , а не только код, вызывающий функцию. Мы не можем использовать код, на который вы ссылаетесь (ссылки являются временными), а некоторые, как я, просто не могут перейти по ссылке, мы просто переходим к следующему вопросу.

2. @Blindman67 я полностью согласен. Я обновляю свой вопрос прямо сейчас в соответствии с вашим комментарием.

3. @Blindman67 Спасибо, что хотя бы заглянули в него. Опять же, я полностью согласен с вами. Но размер составляет 64000 пикселей, это потому, что у нас есть механизм панорамирования. Это означает, что вы можете прокручивать / перемещать холст (влево, вправо, сверху, снизу), который на самом деле имеет 8000 пикселей, и, кроме того, у нас есть функции увеличения и уменьшения масштаба, которые делают холст маленьким и большим, и, соответственно, мы должны изменить разметку на линейке / шкале. Вот почему необходимо иметь такой большой размер. Я могу переосмыслить эту часть, но для ее рефакторинга потребуется много усилий.

4. В демонстрационном приложении я отображаю только вертикальный масштаб, но в моем реальном приложении у нас также реализована горизонтальная линейка / масштаб. Теперь проблема в том, что горизонтальные масштабы с одинаковым размером (64000) работают без каких-либо задержек, но вертикальный масштаб требует времени. В это было бы трудно поверить, но это реальность.

5. Посмотрите на карты Google, там есть огромная виртуальная карта, которую можно увеличивать и уменьшать, панорамировать и т.д., Но холст никогда не становится больше, чем разрешение дисплея устройства. Слишком большой размер холста заставит графический процессор заменять оперативную память при рендеринге разных частей холста. Как только вы превысите лимит оперативной памяти, рендеринг даже одного пикселя будет очень медленным. Простой факт заключается в том, что вы не можете иметь холст намного больше, чем разрешение дисплея, и ожидать производительности 60 кадров в секунду. Кстати, вы находитесь прямо на краю максимальной области холста, см. Ссылку developer.mozilla.org/en-US/docs/Web/HTML/Element /…

Ответ №1:

Это не drawBlackMarkers само по себе, это:

 for (let y = (initialValY), scaleCountY = scaleCountStartValueOfY;
  y <= (rangeValY) amp;amp; scaleCountY <= scaleCountRangeValueOfY;
  y  = scaleInceremnt, scaleCountY  = incrementFactor) {
  

Это постоянно увеличивается и происходит 640 000 раз. Вы можете сказать, что это так, написав:

   // to draw scale lines on canvas
  // drawBlackMarkers(y, coordinateMeasurment);
  console.log(y);
  

и вижу результат консоли.

Так что цикл for делает очень мало, потому что большая его часть находится за оператором switch, и когда он делает даже это простое drawBlackMarkers , за пределами его отображения истинная стоимость этого цикла. rangeValY равно 640 000, что означает, что путь, который должен построить контекст холста, огромен.

Поэтому, чтобы исправить это, вы должны найти способ улучшить эту проблему.

Комментарии:

1. Да, я полностью согласен, что у него много итераций, но без drawBlackMarkers это быстро, потому что он не рисует 2D-графику на холсте, но с drawBlackMarkers это делает. Я работаю над этим, чтобы уменьшить количество итераций. Огромное спасибо за ваш вклад.

Ответ №2:

Это делает много ненужной работы

Высота экрана не составляет 64000 пикселей. Вы хотите вычислить окно просмотра и рисовать только то, что находится в окне просмотра.

Ваша функция drawBlackMarkers не является виновником. До этого система работала очень медленно, просто добавляя еще одну вещь для рисования. Это была соломинка, которая сломала спину верблюду.

Уменьшая длину того, что вы рисуете, вы можете очень легко избежать ненужных циклов процессора.

В этой версии все, что я сделал, это повторно включил drawBlackMarkers и уменьшил холст.

 const CANVAS_WIDTH = 2000;
const CANVAS_HEIGHT = 50;
const completeMarkHight = 15;
const divisibleValue = 0;
const scaleIncementValue = 10;
const scaleTextPadding = { initial: 0, middle: 5, end: 10, complete: 15, alignment: 18 };
const displayScale = { starting: 0, middle: 5, end: 9 };
const colors = { red: '#FF0000', white: '#D5D6D7', black: '#181c21' };
const marktype = { bigMark: 0, middleMark: 5, smallMark: 10 };
const startingInitialOrigin = { x: 0, y: 0 };
const scaleNumberFont = '10px Titillium Web Regular';
const defaultZoomLevel = 100;
const markingGap = {level1: 400, level2: 200, level3: 100, level4: 50, level5: 20, level6: 10 };
const zoomScaleLevel = {level0: 0, level1: 25, level2: 50, level3: 100, level4: 200, level5: 500, level6: 1000};



var $canvas = $('#canvas');
var ctxLeft = $canvas[0].getContext('2d');
var mousePositionCoordinates;
var pagePositions = { x: 100, y:0 };
var  remainderX;
var  remainderY;
var  scaleCountRemainderX;
var  scaleCountRemainderY;
var  zoomFactor;
var  zoomScale;
var  zoomLevel;
var  multiplyFactor;
var  incrementFactor;
var  markingDistance;
var  timetaken=0;
ctxLeft.fillStyle = colors.white;

function render() {
    clear();
    ctxLeft.beginPath();
    zoomScale = 1000;
    zoomLevel = 1000;
    zoomFactor = zoomLevel / defaultZoomLevel;
    markingDistance = markingGap.level6;
    multiplyFactor = markingDistance / defaultZoomLevel;
    incrementFactor = markingDistance / scaleIncementValue; 

    renderVerticalRuler(startingInitialOrigin.y);
   
}

 function renderVerticalRuler(posY) {
     
    
    const initialValY = - posY / multiplyFactor;
    const rangeValY = (CANVAS_WIDTH - posY) / multiplyFactor;

    const initialValOfYwithMultiplyFactor = -posY;
    const rangeValOfYwithMultiplyFactor = (CANVAS_WIDTH - posY);


    // to adjust scale count get remainder value based on marking gap
    scaleCountRemainderY = initialValOfYwithMultiplyFactor % markingDistance;
    const scaleCountStartValueOfY = initialValOfYwithMultiplyFactor - scaleCountRemainderY;
    const scaleCountRangeValueOfY = rangeValOfYwithMultiplyFactor - scaleCountRemainderY;

    // to get orgin(0,0) values
    remainderY = initialValY % 100;
    const translateY = (posY / multiplyFactor) - remainderY;

    ctxLeft.translate(origin.x, translateY); // x,y
    const coordinateMeasurment = 0;

    const t0 = performance.now();
    setMarkers(initialValY, rangeValY, coordinateMeasurment, divisibleValue, scaleCountStartValueOfY, scaleCountRangeValueOfY);

    const t1 = performance.now()
    console.log("it took "   (t1 - t0)   " milliseconds.");

    ctxLeft.stroke();
    ctxLeft.closePath();
  }
  
function setMarkers(initialValY, rangeValY, coordinateMeasurmentr, divisableVal,
    scaleCountStartValueOfY, scaleCountRangeValueOfY) {
    let count = 0;
    // re-modifying scale staring and ending values based on zoom factor
    const scaleInceremnt = scaleIncementValue;
    for (let y = (initialValY), scaleCountY = scaleCountStartValueOfY;
      y <= (rangeValY) amp;amp; scaleCountY <= scaleCountRangeValueOfY;
      y  = scaleInceremnt, scaleCountY  = incrementFactor) {


      switch (count) {
        case displayScale.starting:
          coordinateMeasurment = marktype.bigMark; count  ;
          const scaleValY = scaleCountY - divisableVal;

          ctxLeft.strokeStyle = colors.black;

          ctxLeft.font = scaleNumberFont;
          const size = ctxLeft.measureText(scaleValY.toString());
          ctxLeft.save();
          const textX = coordinateMeasurment   ((size.width) / 2);
          const textY = y - scaleTextPadding.alignment;
          ctxLeft.translate(textX, textY);
          ctxLeft.rotate(-Math.PI / 2);
          ctxLeft.translate(-textX, -textY);
          ctxLeft.fillText(scaleValY.toString(), coordinateMeasurment, y - scaleTextPadding.complete);
          ctxLeft.restore();
          break;
        case displayScale.middle:
          coordinateMeasurment = marktype.middleMark; count  ;
          break;
        case displayScale.end:
          coordinateMeasurment = marktype.smallMark; count = 0;
          break;
        default:
          coordinateMeasurment = marktype.smallMark; count  ;
          break;
      }

      // to draw scale lines on canvas
   drawBlackMarkers(y, coordinateMeasurment);
    }
  }
  
  
 function drawBlackMarkers(y, coordinateMeasurment){
    const markHightY = scaleTextPadding.initial;
    ctxLeft.moveTo(coordinateMeasurment, y   markHightY);
    ctxLeft.lineTo(completeMarkHight, y   markHightY);
  }
  
  
function clear() {
    ctxLeft.resetTransform();
    ctxLeft.clearRect(origin.x, origin.y, CANVAS_HEIGHT, CANVAS_WIDTH);
}


render();
$('.canvas-container').mousemove(function(e) {
   
    
    mousePositionCoordinates = {x:e.clientX, y:e.clientY};
             
        render();
        
        // SHOW RED INDICATOR 
        ctxLeft.beginPath();
        ctxLeft.strokeStyle = colors.red;  // show mouse indicator
        ctxLeft.lineWidth = 2;

        // to display purple indicator based on zoom level
        const mouseX = mousePositionCoordinates.x * zoomFactor;
        const mouseY = mousePositionCoordinates.y * zoomFactor;
        const markHightY =scaleTextPadding.initial   this.remainderY;
        ctxLeft.moveTo(marktype.bigMark, e.clientY );
        ctxLeft.lineTo(completeMarkHight, e.clientY);
        ctxLeft.stroke();
        $('.mouselocation').text(`${mousePositionCoordinates.x},${mousePositionCoordinates.y}`);
   
});  
 body, html{
  width: 100000px;
  height:100000px;
}
.canvas-container{
    width:100%;
    height:100%;
}

.canvasLeft {
    position: absolute;
    border:1px solid black;
    background: grey;
    border-top: none;
    z-index: 1;
    top:0
}


.mouselocation{
  position: fixed;
    right: 0px;
    top: 50px;
}  
 <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<div class="canvas-container">
  <canvas id="canvas" class="canvasLeft" width="30" height="2000"></canvas>
</div>


<div class="mouselocation">
   
</div>