как исправить бликинг шаров в канве?

Тип объекта ball создается с помощью новой функции конструктора ball(color). Его свойства включают в себя все, что нужно, чтобы нарисовать его на холсте и заставить его двигаться в произвольном направлении. Когда создается один шар, OrangeBall = new ball('orange'), он хорошо отображается, включая его движение на холсте. Но когда добавляется еще один, они оба начинают мигать. Как это решить? Спасибо.

    <!DOCTYPE html>
    <html lang="he">
    <head>
        <meta charset="utf-8" />
        <title>
        כדורים קופצים רנדומלית בצבעים שונים
        </title>
        <style>
            html,body {
                margin: 0;
                padding: 0;
                background: black;
            }
            .container { width:900px; margin:0 auto; }
            #canvas { background:#8613eb; border:1px solid #cbcbcb; }
        </style>
        <script>
var ctx;
var H = 800;
var W = 800;

window.onload = function () {

    ctx = canvas.getContext("2d");
    canvas.width=W;
    canvas.height=H;

    function ball(color) {
        //life - the amount of time to show the ball in the screen
        this.life = 60*1000, //1 minute
    this.color = arguments.length==1 ? color: 'white';
    this.x= Math.round(Math.random()*W);
    this.y= Math.round(Math.random()*H);
    this.radius= 10 + Math.round(Math.random()*50);// between 10-60
    this.dx=1+ Math.round(Math.random()*5); //between 1-6
    this.dy= 2+ Math.round(Math.random()*4); //between 2-6
    this.startAngel= 0;
    this.endAngel= 2*Math.PI; //360deg 
    this.speed= 3+Math.round(Math.random()*50) //3-50msec
    this.show = function() {
        //first clear the previous ball 
        ctx.clearRect(0,0,canvas.width,canvas.height);
        var xClear = (this.x-this.radius) <=0 ? 0:(this.x - this.radius);
        var yClear = (this.y-2*this.radius) <=0 ? 0:(this.y - 2*this.radius);
        ctx.clearRect(xClear,yClear,canvas.width,canvas.height);
        //lets stroke the ball
        ctx.beginPath();
        ctx.fillStyle = this.color;
        this.x+=this.dx;
        this.y+=this.dy;
        if (this.x<0 || this.x>W) {
            this.dx=-this.dx;
            }
        if (this.y<0 || this.y>H) {
            this.dy=-this.dy;
            }
        ctx.arc(this.x,this.y,this.radius,this.startAngel,this.endAngel);
        ctx.closePath();
        ctx.fill();
        this.life-=this.speed;
        var _this = this;
        //  creating new property in the ball 'timePointer'
        this.timePointer = this.life<=0 ? 
            clearInterval(this.timePointer): 
            setTimeout(function() {
            _this.show();
            },this.speed);      
    }
    this.show();
    };

    orangeBall = new ball('orange');
    blackBall = new ball('black');
//  whiteBall = new ball('white');
//  yellowgeBall = new ball('yellow');
//  pinkBall = new ball('pink');
//  blueBall = new ball('blue');
//  greenBall = new ball('green');

};
        </script>
    </head>
    <body>
        <div class="container">
            <canvas id="canvas">Your browser doesn't support this game.</canvas>
        </div>
    </body>
</html>

person Zohar Chiprut    schedule 17.05.2017    source источник
comment
взгляните на прототипирование и наследование . Ваш код замедлит работу браузера, если у вас много шаров...   -  person Jonas Wilms    schedule 17.05.2017


Ответы (3)


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

person zero298    schedule 17.05.2017
comment
@Jonasw, правда и отредактировано, хотя если вы зацикливаетесь вечно, это не имеет значения. draw -> clear -> draw -> clear против clear -> draw -> clear -> draw. Вы получите пустой холст в конце прогона в одном направлении, но он будет эффективно анимировать то же самое. - person zero298; 17.05.2017
comment
но если у вас есть тайм-аут между ними, это действительно имеет значение clear-›await-›draw против draw-›await-›clear (и отсутствие какого-либо тайм-аута заблокирует браузер...) - person Jonas Wilms; 17.05.2017
comment
@Jonasw Ты прав, я забыл об этом. И если вы оптимизируете отрисовку только тогда, когда что-то меняется, правильным подходом будет очистка в первую очередь. - person zero298; 17.05.2017
comment
clear-›draw-›clear-›draw.. Вы забываете, что нарисованное нужно представить. Его ясно -> рисовать -> отображать -> ясно -> рисовать -> отображать. Весь рендеринг выполняется в задних буферах, содержимое не отображается до выхода из функции. Это означает, что вы никогда не должны использовать интервал или время ожидания для рендеринга, поскольку содержимое холста помещается в оперативную память дисплея, как только функция завершает работу. Используйте requestAnimationFrame, поскольку это будет удерживать новые пиксели в заднем буфере до вертикального обновления дисплея. - person Blindman67; 17.05.2017

Анимации разбиты на кадры. Кадр составляет примерно 1/60 секунды, и за это время отрисовывается вся анимация.

Чтобы помочь с анимацией, в браузере есть функция, которую вы используете для вызова функции, которая визуализирует ваш кадр. requestAnimationFrame(yourfunction)

requestAnimationFrame сообщит браузеру, что вы вносите изменения в анимацию. Это останавливает представление холста на дисплее до следующего вертикального обновления дисплея.

Использование setInterval или setTimeout

function animateSomething(){
    // draw something    

} // as soon as this exits the content of the canvas is moved to the display
  // there is no way to know where the display hardware is writing pixels to 
  // display, could be halfway through drawing a ball
setInterval(animateSomething,1000 / 60);

Если вы делаете это для многих объектов, каждый раз, когда вы выходите, пиксели перемещаются на дисплей независимо от того, что делает аппаратное обеспечение дисплея. Это вызывает мерцание, сдвиг и другие проблемы.

Использование requestAnimationFrame

function animateSomething(){
    // draw something 
    requestAnimationFrame(animateSomething)   

} // The content of the canvas does not move to the display until the
  // display hardware is getting ready to scan the next display refresh
requestAnimationFrame(animateSomething);

Лучший способ обработки анимаций — выполнять весь рендеринг из одной функции.

Ниже я изменил ваш код, чтобы удалить мерцание, используя requestAnimationFrame. Я добавил функцию mainLoop, которая рисует все шары. Я позволяю браузеру управлять временем.

var ctx;
var H = 800;
var W = 800;

window.onload = function() {

  ctx = canvas.getContext("2d");
  canvas.width = W;
  canvas.height = H;

  function ball(color) {
    //life - the amount of time to show the ball in the screen
    this.life = 60 * 1000; //1 minute
    this.color = arguments.length == 1 ? color : 'white';
    this.x = Math.round(Math.random() * W);
    this.y = Math.round(Math.random() * H);
    this.radius = 10 + Math.round(Math.random() * 50); // between 10-60
    this.dx = 1 + Math.round(Math.random() * 5); //between 1-6
    this.dy = 2 + Math.round(Math.random() * 4); //between 2-6
    this.startAngel = 0;
    this.endAngel = 2 * Math.PI; //360deg 
    this.speed = 3 + Math.round(Math.random() * 50) //3-50msec
    this.show = function() {
      //first clear the previous ball 
      ctx.beginPath();
      ctx.fillStyle = this.color;
      this.x += this.dx;
      this.y += this.dy;
      if (this.x < 0 || this.x > W) {
        this.dx = -this.dx;
      }
      if (this.y < 0 || this.y > H) {
        this.dy = -this.dy;
      }
      ctx.arc(this.x, this.y, this.radius, this.startAngel, this.endAngel);
      ctx.closePath();
      ctx.fill();
      this.life -= this.speed;
    }
  };

  orangeBall = new ball('orange');
  blackBall = new ball('black');
  
  whiteBall = new ball('white');
  yellowgeBall = new ball('yellow');
  pinkBall = new ball('pink');
  blueBall = new ball('blue');
  greenBall = new ball('green');
  
  var balls = [orangeBall, blackBall, whiteBall, yellowgeBall, pinkBall, blueBall, greenBall];
  function mainLoop(){
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      for(var i = 0; i < balls.length; i++){
          if(balls[i].life > 0){
              balls[i].show();
          }
      }
  
  
     requestAnimationFrame(mainLoop);
  }
  requestAnimationFrame(mainLoop);
  
};
html,
body {
  margin: 0;
  padding: 0;
  background: black;
}

.container {
  width: 900px;
  margin: 0 auto;
}

#canvas {
  background: #8613eb;
  border: 1px solid #cbcbcb;
}
<div class="container">
  <canvas id="canvas">Your browser doesn't support this game.</canvas>
</div>

person Blindman67    schedule 17.05.2017

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

var ctx;
var H = window.innerHeight;
var W = window.innerWidth;

window.onload = function () {

	ctx = canvas.getContext("2d");
	canvas.width=W;
	canvas.height=H;

	function ball(color) {
		//life - the amount of time to show the ball in the screen
		this.life = 60*10, //duration of 600 X 16ms(fram per sec)
		this.color = arguments.length==1 ? color: 'white';
		this.x= Math.round(Math.random()*W);
		this.y= Math.round(Math.random()*H);
		this.radius= 10 + Math.round(Math.random()*50);// between 10-60
		this.dx=1+ Math.round(Math.random()*5); //between 1-6
		this.dy= 2+ Math.round(Math.random()*4); //between 2-6
		this.startAngel= 0;
		this.endAngel= 2*Math.PI; //360deg 
		this.start = null;
	};
	ball.prototype.show = function() {
			//lets stroke the ball
			ctx.beginPath();
			ctx.fillStyle = this.color;
			this.x+=this.dx;
			this.y+=this.dy;
			if (this.x<0 || this.x>W) {
				this.dx=-this.dx;
				}
			if (this.y<0 || this.y>H) {
				this.dy=-this.dy;
				}
			ctx.arc(this.x,this.y,this.radius,this.startAngel,this.endAngel);
			ctx.closePath();
			ctx.fill();
			this.life--;
		};
	var arrBalls = [] ;
	arrBalls.push(new ball('orange'));
	arrBalls.push(new ball('black'));
	arrBalls.push(new ball('white'));
	arrBalls.push(new ball('pink'));
	arrBalls.push(new ball('blue'));
	arrBalls.push(new ball('green'));
	
	// This loop shell be less then 10ms inorder not to maintain
	// 60fps interval of screen hardware rendering
	var balldeth = 0;
	function paint() {
			//clear the canvas once in xframes
			ctx.clearRect(0,0,canvas.width,canvas.height);
			//paint balls in the canvas
			for (var i = 0; i < arrBalls.length; i++ ) {	
				arrBalls[i].life >= 0 ? arrBalls[i].show() : ++balldeth;
			}	
		(balldeth == arrBalls.length) ? console.log("game over"):window.requestAnimationFrame(paint);
	}
	
	window.requestAnimationFrame(paint);
	
};
			html,body {
				margin: 0;
				padding: 0;
				background: black;
			}
			#canvas { background:#8613eb; border:1px solid #cbcbcb; }
	<body>
		<canvas id="canvas">Your browser doesn't support this game.</canvas>
	</body>

введите здесь описание изображения

person Zohar Chiprut    schedule 22.05.2017
comment
Шарики имеют какие-то вибрации. - person Zohar Chiprut; 22.05.2017