HTML5 Canvas의 성능을 최적화하기 위해 캐시를 사용하는 방법에 대한 간략한 설명 program_html5 튜토리얼 기술
- WBOY원래의
- 2016-05-16 15:46:482564검색
캔버스를 너무 많이 사용하면 자동으로 성능 문제를 고려하게 됩니다. 캔버스 애니메이션을 최적화하는 방법은 무엇입니까?
【캐시 사용하기】
캐시를 사용한다는 것은 사전 렌더링에 오프스크린 캔버스를 사용한다는 의미입니다. 즉, 먼저 오프스크린 캔버스에 그린 다음 drawImage를 통해 오프스크린 캔버스를 메인 캔버스에 그리는 것입니다. 많은 사람들이 이것을 오해할 수도 있습니다. 이것은 게임에서 많이 사용되는 이중 버퍼링 메커니즘이 아닌가요?
실제로 게임 프로그래밍에서는 화면 깜박임을 방지하기 위해 이중 버퍼링 메커니즘이 사용됩니다. 따라서 사용자 앞에 캔버스가 표시되고 그림을 그릴 때 화면 내용이 먼저 그려집니다. 배경 캔버스를 그리면 캔버스의 데이터가 전면 캔버스에 그려집니다. 이는 이중 버퍼링이지만 캔버스에는 이중 버퍼링이 없습니다. 최신 브라우저에는 기본적으로 이중 버퍼링 메커니즘이 내장되어 있기 때문입니다. 따라서 오프스크린 캔버스를 사용하는 것은 더블 버퍼링이 아니라 오프스크린 캔버스를 캐시 영역으로 취급하는 것입니다. 캔버스 API 호출 소모를 줄이기 위해 반복적으로 그려야 하는 캐시 화면 데이터입니다.
우리 모두 알고 있듯이 캔버스 API를 호출하면 성능이 소모됩니다. 따라서 반복되는 화면 데이터를 그리려는 경우 오프 스크린 캔버스를 적절하게 사용하면 다음 DEMO를 살펴볼 수 있습니다.
1. 캐시를 사용하지 않습니다
2.캐시를 사용하지만 오프스크린 캔버스의 너비와 높이가 설정되지 않았습니다
3. 캐시를 사용하지만 오프스크린 캔버스의 너비와 높이가 설정되지 않았습니다
4. 캐싱을 사용하고 오프스크린 캔버스의 너비와 높이를 설정합니다
위 DEMO의 성능이 다르다는 것을 알 수 있습니다. 아래에서 이유를 분석해 보겠습니다. 각 원의 스타일을 구현하기 위해 원을 그릴 때 캐싱이 활성화되지 않은 경우 루프 그리기를 사용했습니다. 페이지의 원이 특정 지점에 도달하면 애니메이션의 각 프레임마다 많은 수의 캔버스 API 호출이 필요하고 많은 양의 계산이 필요하므로 브라우저가 아무리 좋아도 가져옵니다. 아래에.
XML/HTML 코드클립보드에 콘텐츠 복사
- ctx.save();
-
var j=0;
-
ctx.lineWidth = borderWidth;
-
for(var i=1;i<this.r;i =borderWidth){
- ctx.beginPath();
- ctx.StrokeStyle = 이것.color[j];
- ctx.arc(this.x , this.y , i , 0 , 2*Math.PI);
- ctx.Stroke();
- j ;
- }
- ctx.restore();
所以, 내 방식의 방법은 다음과 같습니다.
除了创建离屏canvas works 为缓存 륙외, 하단의 현대적인 码中有一点很关键, 就是要设置离屏canvas적 크기와 높이, canvas生成后的默认大小是300 X150; 对于我的代码中每个缓存起来圈圈对象半径最大也就不超过80,所以300X150的大区域,会造成资源浪费,所以就要设置一下离屏캔버스의 크기와 높이, 让它跟缓存起来적 元素大小一致,这样也有利于提高动画性能。上side 4个demo很明显的显示了性能差距,如果没有设置宽高,当页side超过400个圈圈对象时就会卡的不行了,而设置了宽高1000个圈圈对象也不觉得卡。
XML/HTML 코드复复内容到剪贴板
- var 공 = 함수(x , y , vx , vy , useCache){
- this.x = x;
- this.y = y;
- this.vx = vx;
- this.vy = vy;
- this.r = getZ(getRandom(20,40));
- this.color = [];
- this.cacheCanvas = document.createElement("canvas");
- thisthis.cacheCtx = this.cacheCanvas.getContext("2d");
- this.cacheCanvas.width = 2*this.r;
- this.cacheCanvas.height = 2*this.r;
- var num = getZ(this.r/borderWidth);
- for(var j=0;j<번호;j ){
- this.color.push("rgba(" getZ(getRandom(0,255)) "," getZ(getRandom(0,255)) "," getZ(getRandom(0,255)) ",1)");
- }
- this.useCache = useCache;
- if(useCache){
- this.cache();
- }
- }
원 개체를 인스턴스화할 때 캐시 메서드를 직접 호출하고 원 개체의 오프스크린 캔버스에 복잡한 원을 직접 그려서 저장합니다.
XML/HTML 코드클립보드에 콘텐츠 복사
- 캐시:함수(){
- this.cacheCtx.save()
- var j=0;
- this.cacheCtx.lineWidth = borderWidth
for(var - i=1;i<this.r;i =borderWidth){
this.cacheCtx.beginPath() -
- thisthis.cacheCtx.StrokeStyle = this.color[j];
this.cacheCtx.arc(this.r, this.r, i, 0, 2*Math.PI)
- this.cacheCtx.Stroke()
- 제
-
this.cacheCtx.restore() -
-
그런 다음 다음 애니메이션에서는 원 개체의 오프스크린 캔버스를 기본 캔버스에 그리기만 하면 됩니다. 이런 식으로 각 프레임에서 호출되는 canvasAPI에는 다음 문장만 있습니다. -
XML/HTML 코드
클립보드에 콘텐츠 복사
ctx.drawImage(this.cacheCanvas , this.x-this.r , this.y-this.r)
-
이전 for 루프 그리기에 비해 정말 훨씬 빠릅니다. 따라서 벡터 그래픽을 반복적으로 그리거나 여러 그림을 그려야 하는 경우 오프스크린 캔버스를 합리적으로 사용하여 그림 데이터를 미리 캐시할 수 있으므로 각 후속 작업에서 불필요한 성능 소비를 많이 줄일 수 있습니다.
원 개체 1000개에 대한 매끄러운 버전 코드는 다음과 같습니다.
XML/HTML 코드
클립보드에 콘텐츠 복사
- >
-
<html lang="ko" >
-
<머리>
-
<메타 문자 집합="UTF- 8">
-
<스타일>
- 몸{
- 패딩:0;
- 여백:0;
- 오버플로: 숨김;
- }
- #cas{
- 디스플레이: 차단;
- 배경색:rgba(0,0,0,0);
- 여백:자동;
- 테두리:1px 단색;
- }
-
스타일>
-
<제목>테스트제목>
-
머리>
-
<몸>
-
<div >
-
<캔버스 id='cas' 너비="800" 높이="600">브라우저가 캔버스를 지원하지 않습니다캔버스 > ;
- <div style="text- align:center">1000개의 원 개체가 붙어 있지 않습니다div>
-
div>
-
-
<스크립트>
-
var testBox = 함수(){
-
var 캔버스 = 문서.getElementById("cas"),
-
ctx = 캔버스.getContext('2d'),
-
테두리 너비 = 2,
-
공 = []
-
var 공 = 함수(x, y, vx, vy, useCache){
-
this.x = x
-
this.y = y;
-
this.vx = vx
-
this.vy = vy;
-
this.r = getZ(getRandom(20,40));
-
this.color = [];
-
this.cacheCanvas = document.createElement("canvas");
-
thisthis.cacheCtx = this.cacheCanvas.getContext("2d");
-
this.cacheCanvas.width = 2*this.r;
-
this.cacheCanvas.height = 2*this.r;
-
var num = getZ(this.r/borderWidth);
-
for(var j=0;j<번호;j ){
- this.color.push("rgba(" getZ(getRandom(0,255)) "," getZ(getRandom(0,255)) "," getZ(getRandom(0,255)) ",1)");
- }
- this.useCache = useCache;
- if(useCache){
- this.cache();
- }
- }
-
- 함수 getZ(num){
- var 반올림;
- 반올림 = (0.5 num) | 0;
- // 더블비트가 아닙니다.
- 반올림 = ~~ (0.5 num);
- // 마지막으로 왼쪽 비트 시프트입니다.
- 반올림 = (0.5 num) << 0;
-
- 반올림하여 반환합니다.
- }
-
- ball.prototype = {
- 페인트:기능(ctx){
- if(!this.useCache){
- ctx.save();
- var j=0;
- ctx.lineWidth = borderWidth;
- for(var i=1;i<this.r;i =borderWidth){
- ctx.beginPath();
- ctx.StrokeStyle = 이것.color[j];
- ctx.arc(this.x , this.y , i , 0 , 2*Math.PI);
- ctx.Stroke();
- j ;
- }
- ctx.restore();
- } 그 외{
- ctx.drawImage(this.cacheCanvas , this.x-this.r , this.y-this.r);
- }
- },
-
- 캐시:기능(){
- this.cacheCtx.save();
- var j=0;
- this.cacheCtx.lineWidth = borderWidth;
- for(var i=1;i<this.r;i =borderWidth){
- this.cacheCtx.beginPath();
- thisthis.cacheCtx.StrokeStyle = this.color[j];
- this.cacheCtx.arc(this.r , this.r , i , 0 , 2*Math.PI);
- this.cacheCtx.Stroke();
- j ;
- }
- this.cacheCtx.restore();
- },
-
- 이동:기능(){
- this.x = this.vx;
- this.y = this.vy;
- if(this.x>(canvas.width-this.r)||this.x< this.r){
- thisthis.x=this.x<this.r?this.r:(canvas.width-this.r);
- this.vx = -this.vx;
- }
- if(this.y>(canvas.height-this.r)||this.y< this.r){
- thisthis.y=this.y<this.r?this.r:(canvas.height-this.r);
- this.vy = -this.vy;
- }
-
- this.paint(ctx);
- }
- }
-
- var 게임 = {
- init:function(){
- for(var i=0;i<1000;i ){
- var b = new ball(getRandom(0,canvas.width) , getRandom(0,canvas .height) , getRandom(-10 , 10) , getRandom(-10 , 10) , true)
- Balls.push(b);
- }
- },
-
- 업데이트:기능(){
- ctx.clearRect(0,0,canvas.width,canvas.height);
- for(var i=0;i<공.길이;i ){
- 공[i].move();
- }
- },
-
- loop:function(){
- var _this = this;
- this.update();
- RAF(함수(){
- _this.loop();
- })
- },
-
- start:function(){
- this.init();
- this.loop();
- }
- }
-
- window.RAF = (function(){
- return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || 함수 (콜백) {window.setTimeout(콜백, 1000 / 60); };
- })();
-
- 게임 반환;
- }();
-
- 함수 getRandom(a , b){
- 반환 Math.random()*(ba) a;
- }
-
- window.onload = 함수(){
- testBox.start();
- }
- 스크립트>
-
몸>
-
html>
오프 스크린 캔버스에 대한 또 다른 참고 사항이 있습니다. 지속적으로 객체를 생성하고 파괴하는 것이라면, 적어도 위에서 쓴 것처럼 각 객체의 속성을 바인딩하지 않도록 주의하세요. 오프스크린 캔버스를 설정합니다.
이렇게 바인딩하면 객체가 소멸되면 오프스크린 캔버스도 소멸되며, 수많은 오프스크린 캔버스가 지속적으로 생성 및 소멸되므로 캔버스 버퍼가 GPU 리소스가 많아 브라우저가 쉽게 충돌하거나 심각한 프레임 정지가 발생할 수 있습니다. 해결책은 오프스크린 캔버스 배열을 만들고, 충분한 수의 오프스크린 캔버스를 미리 로드하고, 아직 살아있는 객체만 캐시하고, 객체가 소멸되면 캐시를 취소하는 것입니다. 이로 인해 오프스크린 캔버스가 파괴되지는 않습니다.
【requestAnimationFrame 사용】
이에 대해서는 자세히 설명하지 않겠습니다. setTimeout이나 setInterval이 아닌 이것이 애니메이션에 가장 적합한 루프라는 것을 많은 사람들이 알고 있을 것입니다. 호환성 작성법 직접 게시:
XML/HTML 코드클립보드에 콘텐츠 복사
- window.RAF = (function(){
- window.requestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame || 함수(콜백)
- })()
【부동소수점 연산 피하기】
JavaScript는 Math.floor, Math.ceil 및parseInt와 같은 몇 가지 매우 편리한 반올림 메서드를 제공하지만 외국 친구들은 테스트를 수행했으며parseInt 메서드는 몇 가지 추가 작업(예: 데이터가 유효한 값인지 감지하고,parseInt)을 수행합니다. 심지어 매개변수를 먼저 문자열로 변환하기도 합니다!) 따라서 직접parseInt를 사용하는 것은 상대적으로 성능 집약적입니다. 따라서 반올림하는 방법은 외국인이 작성한 매우 영리한 방법을 직접 사용할 수 있습니다.
【canvasAPI 호출을 최대한 줄입니다】
파티클 효과를 만들 때는 원을 최대한 적게 사용하고, 파티클이 너무 작기 때문에 사각형을 사용하는 것이 좋습니다. 그 이유는 이해하기 쉽습니다. 원을 그리려면 세 단계가 필요합니다. 먼저 startPath를 사용한 다음 arc를 사용하여 호를 그린 다음 fill을 사용하여 원을 만듭니다. 하지만 정사각형을 그리려면 fillRect가 하나만 필요합니다. 두 호출의 차이만 있을 뿐이지만 입자 개체의 수가 일정 수준에 도달하면 성능 격차가 나타납니다.
그 밖에도 주의할 사항이 있는데, Google에 꽤 많이 있기 때문에 모두 나열하지는 않겠습니다. 이는 나 자신을 위한 기록이라 할 수 있으며, 주로 캐시의 사용량을 기록하기 위한 것이다. 캔버스의 성능을 향상시키려면 가장 중요한 것은 코드 구조에 주의를 기울이고, 불필요한 API 호출을 줄이고, 각 프레임의 복잡한 작업을 줄이거나, 복잡한 작업을 프레임당 한 번에서 여러 프레임에 한 번으로 변경하는 것입니다. 프레임. 동시에 위에서 언급한 캐시 활용을 위해 각 개체에 대해 오프스크린 캔버스를 사용했습니다. 실제로 오프스크린 캔버스를 너무 많이 사용하면 너무 광범위하게 사용할 수 없습니다. 성능 문제가 발생할 수 있으니 최대한 오프스크린 캔버스를 활용해 보세요.
소스코드 주소 : https://github.com/whxaxes/canvas-test/tree/gh-pages/src/Other-demo/cache