Канвас. Как можно оптимизировать и избавиться от артефактов?

Есть сайт, в нем канвас (самолет), который лагает при прокрутке, появляеются артефакты, причем по разному, в зависимости от того, чем скроллить - колесиком мыши или скроллбаром. Если закомментить параллакс или видео в начале, то ничего не меняется. Во время скролла обращений к ДОМ не происходит. Лаги либо из за скрипта самолета, либо из-за clearRect. Как это можно оптимизировать и избавиться от артефактов? Код на кодпене.

P.S. На кодпене не появляются артефакты, они лишь на сайте!

UPD: Что еще странно, артефакты появляются лишь в Chrome и Opera.

Вот JS-код:
var scrolled;
  var positionsArr = [];
  var commonHeight = 0;
  var canvas = document.querySelector('#canvas');
  var canvasB = document.querySelector('#canvasback');
  var ctx = canvas.getContext('2d');
  var ctxB = canvasB.getContext('2d');
  var centerX, centerY, radius, pointsOffset, radiusRatio, centerXRatio,
    firstPoint, lastPoint, jet, blocksPosition, jetPositionY, jetTop, tabletOffset, headerHeight, canvasClearWidth;
  var wWidth, mediaMobile, mediaTablet, mediaDesktop1, mediaDesktop2, mediaDesktop3;

  jet = new Image();
  jet.src = 'http://silencer.website/alkor/images/jet.png';
  jet.onload = function () {
    adaptive();
  }

  $(window).on('resize', function () {
    adaptive();
  });

  function adaptive() {
    mediaMobile = mediaTablet = mediaDesktop1 = mediaDesktop2 = mediaDesktop3 = false;
    wWidth = $(window).width();

    // Параметры для дуги по умолчанию
    tabletOffset = 0;
    radiusRatio = .95;
    centerXRatio = 1.25;

    // Параметры для дуги на разных разрешениях (перезаписывают параметры по умолчанию)
    if (wWidth < 768) {
      mediaMobile = true;
    }
    if (wWidth >= 768 && wWidth < 1024) {
      mediaTablet = true;
      tabletOffset = 120;
    }
    if (wWidth >= 1024 && wWidth < 1280) {
      mediaDesktop1 = true;
      tabletOffset = -40;
      radiusRatio = 1.1;
      centerXRatio = 1.03;
    }
    if (wWidth >= 1280 && wWidth < 1440) {
      mediaDesktop2 = true;
      tabletOffset = -20;
    }
    if (wWidth >= 1440) {
      mediaDesktop3 = true;
      tabletOffset = -20;
    }

    if (!mediaMobile) {
      setTimeout(function () {
        doCanvas();
      }, 500);
    } else {
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      $(window).off('scroll', onScroll);
    }
  }

  function doCanvas() {
    $(window).on('scroll', onScroll);

    var marginBottom = 120;
    commonHeight = 0;

    $('.history__item').each(function () {
      commonHeight = commonHeight + $(this).height() + marginBottom;
    });
    commonHeight = commonHeight - marginBottom;

    canvasWidth = $('.history').width() * .3;
    canvasHeight = $('.history__blocks').height();
    canvas.width = canvasWidth;
    canvas.height = canvasHeight;

    canvasB.width = canvas.width;
    canvasB.height = canvas.height;

    offsetLeft = $('.history__blocks')[0].offsetLeft;
    $('#canvas, #canvasback').css('marginLeft', -offsetLeft);

    radius = commonHeight * radiusRatio;
    centerX = -commonHeight / centerXRatio + offsetLeft + tabletOffset;
    centerY = commonHeight / 2;
    pointsOffset = 100;

    canvasClearWidth = centerX + radius + 110;

    blocksPosition = $('.history__blocks')[0].offsetTop;
    jetPositionY = $('.history')[0].offsetTop;
    headerHeight = $('.history__title').height();
    jetTop = $(window).height() / 2;

    jetOnScroll = jetTop - headerHeight - jetPositionY;

    positionsArr = [];

    lastIndex = $('.history__item').length - 1;
    darkened = $('.history__item');

    for (var i = 0; i < $('.history__item').length; i++) {
      var position = $('.history__item').eq(i)[0].offsetTop - blocksPosition + pointsOffset;
      positionsArr.push(position);

      if (i == 0) {
        firstPoint = position;
      }

      if (i == $('.history__item').length - 1) {
        lastPoint = position;
      }
    }

    draw();
    drawBackground();
    setTimeout(function () {
      if (mediaTablet) {
        jetPositionLine(firstPoint);
      } else {
        jetPosition(firstPoint);
      }

    }, 100);
  }

  function drawBackground() {
    if (mediaTablet) {
      drawLine();
    } else {
      drawArc();
    }
  }

  function draw(currentY) {
    for (var i = 0; i < positionsArr.length; i++) {
      addPoint(positionsArr[i], currentY, i);
    }
  }

  function drawArc() {
    var firstPointRad = Math.asin((centerY - firstPoint) / radius);
    var lastPointRad = Math.asin((centerY - lastPoint) / radius);

    ctxB.beginPath();
    ctxB.lineWidth = 3;
    ctxB.save();
    ctxB.arc(centerX, centerY, radius, 1.5, lastPointRad, true);
    ctxB.strokeStyle = '#99daf0';
    ctxB.setLineDash([8, 5]);
    ctxB.lineDashOffset = 1;
    ctxB.globalCompositeOperation = 'destination-over';
    ctxB.stroke();
    ctxB.restore();
  }

  function drawLine() {
    ctxB.beginPath();
    ctxB.lineWidth = 3;
    ctxB.save();
    ctxB.lineTo(tabletOffset, firstPoint);
    ctxB.lineTo(tabletOffset, lastPoint);
    ctxB.strokeStyle = '#99daf0';
    ctxB.setLineDash([8, 5]);
    ctxB.lineDashOffset = 1;
    ctxB.globalCompositeOperation = 'destination-over';
    ctxB.stroke();
    ctxB.restore();
  }

  function addPoint(y, currentY, i) {
    if (mediaTablet) {
      var currentX = tabletOffset;
    } else {
      var angle = Math.asin((centerY - y) / radius);
      var currentX = centerX + radius * (Math.cos(angle));
    }

    ctx.beginPath();
    ctx.arc(currentX, y, 8, 0, 2 * Math.PI, false);
    ctx.lineWidth = 3;
    ctx.fillStyle = '#00a3da';
    ctx.globalCompositeOperation = 'source-over';
    if (currentY + 10 >= y) {
      ctx.strokeStyle = '#fff';
      darkened.eq(i).removeClass('darkened');
    } else {
      ctx.strokeStyle = '#99daf0';
      darkened.eq(i).addClass('darkened');
    }

    ctx.fill();
    ctx.stroke();
  }

  function jetPosition(y) {
    var angle = Math.asin((centerY - y) / radius);
    var currentX = centerX + radius * (Math.cos(angle) - 1);

    var firstPointRad = Math.asin((centerY - firstPoint) / radius);

    // if (toUp) {   // Самолетик вверх-вниз
    var jetAngle = Math.acos((centerY - y) / radius);
    // } else {
    // 	var jetAngle = Math.acos((centerY - y) / radius) + Math.PI;
    // }

    ctx.beginPath();
    ctx.arc(centerX, centerY, radius, -angle, -firstPointRad, true);
    ctx.globalCompositeOperation = 'source-over';
    ctx.lineWidth = 3;
    ctx.strokeStyle = '#fff';
    ctx.stroke();

    draw(y);

    ctx.save();
    ctx.translate(currentX + radius, y)
    ctx.rotate(jetAngle - 1.6);
    ctx.globalCompositeOperation = 'source-over';
    ctx.drawImage(jet, -106, -80, 212, 160);
    ctx.restore();
  }

  function jetPositionLine(y) {
    ctx.beginPath();
    ctx.lineTo(tabletOffset, firstPoint);
    ctx.lineTo(tabletOffset, y);
    ctx.lineWidth = 3;
    ctx.strokeStyle = '#fff';
    ctx.stroke();

    draw(y);

    ctx.beginPath();
    ctx.save();
    ctx.globalCompositeOperation = 'source-over';
    ctx.drawImage(jet, tabletOffset - 106, y - 80, 212, 160);
    ctx.restore();
  }

  function onScroll() {
    requestAnimationFrame(scrollCanvas);
  };

  function scrollCanvas() {
    var prevScroll = scrolled;
    scrolled = window.pageYOffset;

    toUp = prevScroll < scrolled;
    var positionY = scrolled + jetOnScroll;
    if (positionY >= firstPoint) {
      if (positionY <= lastPoint) {
        ctx.clearRect(0, 0, canvasClearWidth, canvasHeight);

        if (mediaTablet) {
          jetPositionLine(positionY);
        } else {
          jetPosition(positionY);
        }
      } else {
        $('.history__item').eq(lastIndex).removeClass('darkened');
      }
    }
    if (!mediaMobile && !mediaTablet) {
      parallaxScroll();
    }


    if (prevScroll > scrolled) {
      paraCounter--;
    } else {
      paraCounter++;
    }

  }
  • Вопрос задан
  • 685 просмотров
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы