# aardio 范例: canvas 动效画布：跟随显示器刷新的流畅动画



```aardio
//canvas 动效画布：跟随显示器刷新的流畅动画
//自绘事件与动画接口和 plus 控件相同，画布是常驻的 PARGB 内存位图，比 plus 快数倍
import win.ui;
/*DSG{{*/
var winform = win.form(text="canvas 动效画布 - 点击添加小球";right=759;bottom=479)
winform.add(
canvas={cls="canvas";left=0;top=0;right=760;bottom=480;bgcolor=0x2A1A12;anchors={left=1;top=1;right=1;bottom=1};z=1}
)
/*}}*/

var balls = {};
var addBall = function(x,y){
	table.push(balls,{
		x = x; y = y;
		vx = math.random(-300,300); vy = math.random(-500,-150);
		r = math.random(10,26);
		color = 0xFF000000 | math.random(0x404040,0xFFFFFF);
	})
}
for(i=1;20) addBall(math.random(100,660),math.random(80,300));

//画刷、字体创建一次反复使用
var brush = gdip.solidBrush(0xFFFFFFFF);
var textBrush = gdip.solidBrush(0xCCFFFFFF);
var font = gdip.font("Segoe UI",14);
var fps = 0;

//画布的坐标是设备像素，先缩放到 100% DPI 的逻辑坐标再画
winform.canvas.onDrawForeground = function(graphics,rc){
	var dpi = winform.dpiScale(1);
	graphics.scale(dpi,dpi);
	
	for(i,b in balls){
		brush.color = b.color;
		graphics.fillCircle(brush,b.x,b.y,b.r);
	}
	graphics.drawString(#balls ++ " 个小球   " ++ fps ++ " 帧/秒",font,12,10,textBrush);
}

//每帧回调：按真实时间（deltaMs）推进，回调后自动重绘
var frames,since = 0,0;
winform.canvas.onAnimation = function(prevState,beginning,change,timestamp,duration,deltaMs){
	var dt = math.min(deltaMs,50) / 1000; //限制异常大帧（例如窗口最小化后恢复）
	var dpi = winform.dpiScale(1);
	var rc = owner.getClientRect();
	var w,h = rc.width / dpi,rc.height / dpi;
	
	for(i,b in balls){
		b.vy = b.vy + 900 * dt;
		b.x = b.x + b.vx * dt;
		b.y = b.y + b.vy * dt;
		if( (b.x < b.r && b.vx < 0) || (b.x > w - b.r && b.vx > 0) ) b.vx = -b.vx;
		if(b.y > h - b.r && b.vy > 0){
			b.y = h - b.r;
			b.vy = -b.vy * 0.9;
		}
	}
	
	frames++;
	since = since + deltaMs;
	if(since >= 500){
		fps = math.round(frames * 1000 / since);
		frames,since = 0,0;
	}
	return true; //返回 null 才会停止动画
}

winform.canvas.onMouseDown = function(wParam,lParam){
	var x,y = win.getMessagePos(lParam);
	var dpi = winform.dpiScale(1);
	for(i=1;5) addBall(x / dpi,y / dpi);
}

winform.onDestroy = function(){
	brush.delete();
	textBrush.delete();
	font.delete();
}

winform.canvas.startAnimation(); //省略间隔：每帧回调（跟随显示器刷新率）
winform.show();
win.loopMessage();

```

