내 배경
<canvas>
는 2006년 Firefox v2.0이 출시되었을 때 처음 알게 되었습니다. 변환 행렬을 설명하는 Ajaxian의 기사를 보고 첫 번째 <canvas>
웹 앱인 Color Sphere (2007)를 만들었습니다. 이로 인해 색상과 그래픽 원시 항목의 세계에 몰입하게 되었고, 브라우저에서 'Paint보다 나은' 애플리케이션을 만들기 위해 Sketchpad (2007~2008)를 만들게 되었습니다.
이러한 실험을 통해 오랜 친구인 찰스 프리처드와 함께 Mugtug라는 스타트업을 만들게 되었습니다. YouTube는 HTML5 <canvas>
에서 Darkroom을 개발하고 있습니다. Darkroom은 비파괴적 사진 공유 앱으로, 픽셀 기반 필터의 기능을 벡터 기반 서체 및 그리기와 결합합니다.
소개
<canvas>
를 사용하면 JavaScript 프로그래머가 모니터의 시각적 구성요소인 화면의 색상, 벡터, 픽셀을 완전히 제어할 수 있습니다.
다음 예에서는 <canvas>
에서 주목받지 못했던 한 영역, 즉 텍스트 효과를 만드는 방법을 설명합니다. <canvas>
에서 만들 수 있는 다양한 텍스트 효과는 상상할 수 없을 만큼 많습니다. 이 데모에서는 가능한 효과의 하위 섹션을 다룹니다. 이 도움말에서는 '텍스트'를 다루지만 이 메서드는 모든 벡터 객체에 적용할 수 있으므로 게임 및 기타 애플리케이션에서 흥미로운 시각적 효과를 만들 수 있습니다.
<canvas>
의 Text-Shadows<canvas>
에서 클리핑 마스크를 만들고,<canvas>
에서 측정항목을 찾고, 그림자 속성을 사용하는 CSS와 유사한 텍스트 효과- 네온 레인보우, 얼룩말 반사 - 체이닝 효과
- globalCompositeOperation, createLinearGradient, createPattern 사용의
<canvas>
예시에서 Photoshop과 같은 텍스트 효과 <canvas>
의 내부 및 외부 그림자- 잘 알려지지 않은 기능을 공개합니다. 시계 방향과 시계 반대 방향으로 와인딩하여 음영 (내부 음영)의 역을 만듭니다.
- Spaceage - 생성형 효과
<canvas>
에서 hsl() 색상 순환 및window.requestAnimationFrame
를 사용하여 생성형 기반 텍스트 효과를 적용하여 모션 느낌을 만듭니다.
캔버스의 텍스트 음영
CSS3 사양에 추가된 기능 중 border-radius, web-gradients 등과 함께 가장 좋아하는 기능은 그림자를 만드는 기능입니다. CSS와 <canvas>
그림자의 차이점을 이해하는 것이 중요합니다. 특히 다음 사항에 유의하세요.
CSS는 두 가지 메서드를 사용합니다. div, span 등과 같은 박스 요소에는 box-shadow를, 텍스트 콘텐츠에는 text-shadow를 사용합니다.
<canvas>
에는 하나의 유형의 그림자가 있습니다. 모든 벡터 객체(ctx.moveTo, ctx.lineTo, ctx.bezierCurveTo, ctx.quadradicCurveTo, ctx.arc, ctx.rect, ctx.fillText, ctx.strokeText 등)에 사용됩니다.
<canvas>
에서 그림자를 만들려면 다음 네 가지 속성을 탭합니다.
- ctx.shadowColor = "red" // 문자열
- 그늘의 색상입니다. RGB, RGBA, HSL, HEX 및 기타 입력이 유효합니다.
- ctx.shadowOffsetX = 0; // 정수
- 텍스트와 관련된 그림자의 가로 거리입니다.
- ctx.shadowOffsetY = 0; // 정수
- 텍스트와 관련된 그림자의 세로 거리입니다.
- ctx.shadowBlur = 10; // 정수
- 그림자에 적용되는 흐리게 처리 효과입니다. 값이 클수록 흐리게 처리됩니다.
시작하려면 <canvas>
가 CSS 효과를 에뮬레이션하는 방법을 살펴보겠습니다.
Google 이미지에서 'css text-shadow'를 검색한 결과 Line25, Stereoscopic, Shadow 3D와 같이 에뮬레이션할 수 있는 멋진 데모가 몇 개 있었습니다.
입체 3D 효과 (자세한 내용은 아나글립 이미지 참고)는 유용하게 사용되는 간단한 코드 줄의 예입니다. 다음 CSS 줄을 사용하면 3D 빨간색/시안색 안경 (3D 영화에서 제공하는 안경)으로 볼 때 깊이의 환상을 만들 수 있습니다.
text-shadow: -0.06em 0 0 red, 0.06em 0 0 cyan;
이 문자열을 <canvas>
로 변환할 때는 두 가지 사항에 유의해야 합니다.
- shadow-blur (세 번째 값)가 없으므로 fillText를 실행해도 동일한 결과가 생성되므로 shadow를 실제로 실행할 이유가 없습니다.
var text = "Hello world!"
ctx.fillStyle = "#000"
ctx.fillText(text, -7, 0);
ctx.fillStyle = "red"
ctx.fillText(text, 0, 0);
ctx.fillStyle = "cyan"
ctx.fillText(text, 7, 0);</pre>
- EM은
<canvas>
에서 지원되지 않으므로 PX로 변환해야 합니다. DOM에서 동일한 font-properties를 가진 요소를 만들고 너비를 측정할 형식으로 설정하여 PT, PC, EM, EX, PX 등을 변환하는 변환 비율을 찾을 수 있습니다. 예를 들어 EM -> PX 변환을 캡처하려면 'height: 1em'으로 DOM 요소를 측정하면 각 EM에 있는 PX 수가 offsetHeight가 됩니다.
var font = "20px sans-serif"
var d = document.createElement("span");
d.style.cssText = "font: " + font + " height: 1em; display: block"
// the value to multiply PX 's by to convert to EM 's
var EM2PX = 1 / d.offsetHeight;</pre>
알파 곱셈 방지
Line25에 있는 네온 효과와 같이 더 복잡한 예에서는 효과를 적절하게 에뮬레이션하려면 shadowBlur 속성을 사용해야 합니다. 네온 효과는 여러 그림자를 사용하므로 문제가 발생합니다. <canvas>
에서는 각 벡터 객체에 그림자가 하나만 있을 수 있습니다. 따라서 여러 그림자를 그리려면 텍스트의 여러 버전을 그 위에 그려야 합니다.
이로 인해 알파 곱셈이 발생하고 결국 모서리가 들쭉날쭉해집니다.
ctx.fillStyle = "rgba(0,0,0,0)"
또는 "transparent"
를 실행하여 텍스트를 숨기고 그림자를 표시하려고 했지만 소용이 없었습니다. 그림자는 fillStyle 알파의 곱셈이므로 그림자가 fillStyle보다 불투명할 수는 없습니다.
다행히 이 문제를 해결할 방법이 있습니다. 텍스트에서 그림자 오프셋을 그려 서로 분리된 상태로 유지하여 '겹치지 않게' 텍스트를 화면 측면에서 숨길 수 있습니다.
var text = "Hello world!"
var blur = 10;
var width = ctx.measureText(text).width + blur * 2;
ctx.textBaseline = "top"
ctx.shadowColor = "#000"
ctx.shadowOffsetX = width;
ctx.shadowOffsetY = 0;
ctx.shadowBlur = blur;
ctx.fillText(text, -width, 0);
텍스트 블록 주변 클리핑
이를 정리하려면 클리핑 경로를 추가하여 fillText가 처음에 그려지지 않도록 하고 그림자를 그릴 수 있도록 허용하면 됩니다.
텍스트를 둘러싸는 클리핑 패스를 만들려면 텍스트의 높이('em-height'라고 함, 이전에는 인쇄기의 'M' 문자 높이)와 텍스트의 너비를 알아야 합니다.
ctx.measureText().width
를 사용하여 너비를 가져올 수 있지만 ctx.measureText().height
는 존재하지 않습니다.
다행히 CSS 해킹을 통해 (CSS 측정값을 사용하여 이전 <canvas>
구현을 수정하는 더 많은 방법은 서체 측정값 참고) 동일한 font-properties를 사용하여 <span>
의 offsetHeight
를 측정하여 텍스트의 높이를 찾을 수 있습니다.
var d = document.createElement("span");
d.font = "20px arial"
d.textContent = "Hello world!"
var emHeight = d.offsetHeight;
그런 다음 잘라내기 경로로 사용할 직사각형을 만들어 더미 도형을 삭제하면서 '그림자'를 닫습니다.
ctx.rect(0, 0, width, emHeight);
ctx.clip();
모든 것을 연결하고 진행하면서 최적화합니다. 그림자에 블러가 없는 경우 fillText를 사용하여 동일한 효과를 얻을 수 있으므로 클리핑 마스크를 설정할 필요가 없습니다.
var width = ctx.measureText(text).width;
var style = shadowStyles[text];
// add a background to the current effect
ctx.fillStyle = style.background;
ctx.fillRect(0, offsetY, ctx.canvas.width, textHeight - 1)
// parse text-shadows from css
var shadows = parseShadow(style.shadow);
// loop through the shadow collection
var n = shadows.length; while(n--) {
var shadow = shadows[n];
var totalWidth = width + shadow.blur * 2;
ctx.save();
ctx.beginPath();
ctx.rect(offsetX - shadow.blur, offsetY, offsetX + totalWidth, textHeight);
ctx.clip();
if (shadow.blur) { // just run shadow (clip text)
ctx.shadowColor = shadow.color;
ctx.shadowOffsetX = shadow.x + totalWidth;
ctx.shadowOffsetY = shadow.y;
ctx.shadowBlur = shadow.blur;
ctx.fillText(text, -totalWidth + offsetX, offsetY + metrics.top);
} else { // just run pseudo-shadow
ctx.fillStyle = shadow.color;
ctx.fillText(text, offsetX + (shadow.x||0), offsetY - (shadow.y||0) + metrics.top);
}
ctx.restore();
}
// drawing the text in the foreground
if (style.color) {
ctx.fillStyle = style.color;
ctx.fillText(text, offsetX, offsetY + metrics.top);
}
// jump to next em-line
ctx.translate(0, textHeight);
이러한 <canvas>
명령어를 모두 수동으로 입력하는 것은 바람직하지 않으므로 데모 소스에 간단한 text-shadow 파서를 포함했습니다. 이렇게 하면 CSS 명령어를 제공하고 <canvas>
명령어를 생성할 수 있습니다.
이제 <canvas>
요소에 연결할 수 있는 다양한 스타일이 있습니다.
이러한 동일한 그림자 효과는 WebFonts에서 SVG로 가져온 복잡한 도형, 생성형 벡터 도형 등에 이르기까지 모든 벡터 객체에 사용할 수 있습니다.
인터미션 (픽셀 푸시에 관한 보조 설명)
이 도움말의 섹션을 작성할 때 입체 예시가 궁금했습니다. <canvas>
와 약간 다른 관점에서 촬영한 두 이미지를 사용하여 3D 영화 화면 효과를 만드는 것은 얼마나 어려운가요? 그다지 어렵지 않은 것 같습니다. 다음 커널은 첫 번째 이미지 (data)의 빨간색 채널을 두 번째 이미지 (data2)의 청록색 채널과 결합합니다.
data[i] = data[i] * 255 / 0xFF;
data[i+1] = 255 * data2[i+1] / 0xFF;
data[i+2] = 255 * data2[i+2] / 0xFF;
이제 누군가 두 대의 iPhone을 이마에 덕트테이프로 고정하고 동시에 '동영상 녹화'를 클릭하면 HTML5로 나만의 3D 영화를 만들 수 있습니다. 지원하실 분 있나요?
네온 레인보우, 얼룩말 반사 효과 체이닝
<canvas>
에서 여러 효과를 체이닝하는 것은 간단하지만 globalCompositeOperation (GCO)에 관한 기본 지식이 필요합니다. GIMP (또는 Photoshop)와 작업을 비교하려면 다음을 참고하세요. <canvas>
에는 12개의 GCO가 있으며 darker 및 lighter는 레이어 혼합 모드로 간주할 수 있습니다. 나머지 10개 작업은 레이어에 알파 마스크로 적용됩니다 (한 레이어가 다른 레이어의 픽셀을 삭제함). globalCompositeOperation은 '레이어'(이 경우 코드 문자열)를 서로 연결하여 새롭고 흥미로운 방식으로 결합합니다.
globalCompositeOperation 차트는 작동 중인 GCO 모드를 보여줍니다. 이 차트는 예상되는 결과를 자세히 확인하기 위해 색상 스펙트럼의 상당 부분과 여러 수준의 알파 투명도를 사용합니다. 텍스트 설명은 Mozilla의 globalCompositeOperation 참조를 확인하세요. 자세한 내용은 Porter Duff의 디지털 이미지 합성에서 작업이 작동하는 방식을 참고하세요.
가장 좋아하는 모드는 globalCompositeOperation="lighter"입니다. Lighter는 빛이 혼합되는 방식과 유사하게 추가된 픽셀을 혼합합니다. 빨간색, 녹색, 흰색 빛이 최대 강도로 표시되면 흰색 빛이 보입니다. 특히 <canvas>
가 낮은 globalAlpha로 설정된 경우 이 기능을 사용해 볼 만합니다. 그러면 더 세밀하게 제어하고 더 부드러운 가장자리 효과를 얻을 수 있습니다. 라이터는 다양한 용도로 사용되고 있습니다. 최근에 가장 좋아하는 기능은 http://weavesilk.com/에서 제공하는 HTML5 데스크톱 배경 제작기입니다.
내 데모 중 하나인 Breathing Galaxies (JS1k)도 밝은 모드를 사용합니다. 이 두 가지 예시에서 패턴을 그리면 이 모드가 어떤 효과를 내는지 확인할 수 있습니다.
globalCompositeOperation 브라우저 처리
네온 무지개 지터 효과
다음 데모에서는 globalCompositeOperation (source-in, lighter, darker)을 사용하여 효과를 함께 연결하여 Photoshop과 같은 흔들리는 윤곽선이 있는 네온 레인보우 발광 효과를 만들어 보겠습니다.
이 데모는 텍스트에서 그림자를 분리하는 데 동일한 전략을 사용하는 '<canvas>
의 텍스트 그림자' 데모의 연장선입니다 (이전 섹션 참고).
function neonLightEffect() {
var text = "alert('"+String.fromCharCode(0x2665)+"')";
var font = "120px Futura, Helvetica, sans-serif";
var jitter = 25; // the distance of the maximum jitter
var offsetX = 30;
var offsetY = 70;
var blur = getBlurValue(100);
// save state
ctx.save();
ctx.font = font;
// calculate width + height of text-block
var metrics = getMetrics(text, font);
// create clipping mask around text-effect
ctx.rect(offsetX - blur/2, offsetY - blur/2,
offsetX + metrics.width + blur, metrics.height + blur);
ctx.clip();
// create shadow-blur to mask rainbow onto (since shadowColor doesn't accept gradients)
ctx.save();
ctx.fillStyle = "#fff";
ctx.shadowColor = "rgba(0,0,0,1)";
ctx.shadowOffsetX = metrics.width + blur;
ctx.shadowOffsetY = 0;
ctx.shadowBlur = blur;
ctx.fillText(text, -metrics.width + offsetX - blur, offsetY + metrics.top);
ctx.restore();
// create the rainbow linear-gradient
var gradient = ctx.createLinearGradient(0, 0, metrics.width, 0);
gradient.addColorStop(0, "rgba(255, 0, 0, 1)");
gradient.addColorStop(0.15, "rgba(255, 255, 0, 1)");
gradient.addColorStop(0.3, "rgba(0, 255, 0, 1)");
gradient.addColorStop(0.5, "rgba(0, 255, 255, 1)");
gradient.addColorStop(0.65, "rgba(0, 0, 255, 1)");
gradient.addColorStop(0.8, "rgba(255, 0, 255, 1)");
gradient.addColorStop(1, "rgba(255, 0, 0, 1)");
// change composite so source is applied within the shadow-blur
ctx.globalCompositeOperation = "source-atop";
// apply gradient to shadow-blur
ctx.fillStyle = gradient;
ctx.fillRect(offsetX - jitter/2, offsetY,
metrics.width + offsetX, metrics.height + offsetY);
// change composite to mix as light
ctx.globalCompositeOperation = "lighter";
// multiply the layer
ctx.globalAlpha = 0.7
ctx.drawImage(ctx.canvas, 0, 0);
ctx.drawImage(ctx.canvas, 0, 0);
ctx.globalAlpha = 1
// draw white-text ontop of glow
ctx.fillStyle = "rgba(255,255,255,0.95)";
ctx.fillText(text, offsetX, offsetY + metrics.top);
// created jittered stroke
ctx.lineWidth = 0.80;
ctx.strokeStyle = "rgba(255,255,255,0.25)";
var i = 10; while(i--) {
var left = jitter / 2 - Math.random() * jitter;
var top = jitter / 2 - Math.random() * jitter;
ctx.strokeText(text, left + offsetX, top + offsetY + metrics.top);
}
ctx.strokeStyle = "rgba(0,0,0,0.20)";
ctx.strokeText(text, offsetX, offsetY + metrics.top);
ctx.restore();
};
얼룩말 반사 효과
Zebra Reflection 효과는 CSS로 페이지를 꾸미는 방법에 관한 WebDesignerWall의 훌륭한 리소스에서 영감을 얻었습니다. 이 아이디어를 조금 더 발전시켜 iTunes에서 볼 수 있는 것과 같은 텍스트의 '반사'를 만들 수 있습니다. 이 효과는 fillColor (white), createPattern (zebra.png), linearGradient (shine)를 결합합니다. 이는 각 벡터 객체에 여러 채우기 유형을 적용하는 기능을 보여줍니다.
function sleekZebraEffect() {
// inspired by - http://www.webdesignerwall.com/demo/css-gradient-text/
var text = "Sleek Zebra...";
var font = "100px Futura, Helvetica, sans-serif";
// save state
ctx.save();
ctx.font = font;
// getMetrics calculates:
// width + height of text-block
// top + middle + bottom baseline
var metrics = getMetrics(text, font);
var offsetRefectionY = -20;
var offsetY = 70;
var offsetX = 60;
// throwing a linear-gradient in to shine up the text
var gradient = ctx.createLinearGradient(0, offsetY, 0, metrics.height + offsetY);
gradient.addColorStop(0.1, '#000');
gradient.addColorStop(0.35, '#fff');
gradient.addColorStop(0.65, '#fff');
gradient.addColorStop(1.0, '#000');
ctx.fillStyle = gradient
ctx.fillText(text, offsetX, offsetY + metrics.top);
// draw reflected text
ctx.save();
ctx.globalCompositeOperation = "source-over";
ctx.translate(0, metrics.height + offsetRefectionY)
ctx.scale(1, -1);
ctx.font = font;
ctx.fillStyle = "#fff";
ctx.fillText(text, offsetX, -metrics.height - offsetY + metrics.top);
ctx.scale(1, -1);
// cut the gradient out of the reflected text
ctx.globalCompositeOperation = "destination-out";
var gradient = ctx.createLinearGradient(0, offsetY, 0, metrics.height + offsetY);
gradient.addColorStop(0.0, 'rgba(0,0,0,0.65)');
gradient.addColorStop(1.0, '#000');
ctx.fillStyle = gradient;
ctx.fillRect(offsetX, offsetY, metrics.width, metrics.height);
// restore back to original transform state
ctx.restore();
// using source-atop to allow the transparent .png to show through to the gradient
ctx.globalCompositeOperation = "source-atop";
// creating pattern from <image> sourced.
ctx.fillStyle = ctx.createPattern(image, 'repeat');
// fill the height of two em-boxes, to encompass both normal and reflected state
ctx.fillRect(offsetX, offsetY, metrics.width, metrics.height * 2);
ctx.restore();
};
캔버스의 내부/외부 그림자
<canvas>
사양은 '내부'와 '외부' 그림자의 주제를 다루지 않습니다. 사실 처음에는 '내부' 그림자가 지원되지 않는 것처럼 보일 수 있습니다. 호출은 건너뛸 수 없습니다.
사용 설정하는 것이 약간 더 까다롭습니다. ;) F1LT3R의 최근 게시물에서 제안한 대로 시계 방향 및 시계 반대 방향 와인딩 규칙의 고유한 속성을 사용하여 내부 그림자를 만들 수 있습니다. 이렇게 하려면 컨테이너 직사각형을 그려 'inner-shadow'를 만든 다음 반대 와인딩 규칙을 사용하여 잘라낸 도형을 그려 도형의 역상을 만듭니다.
다음 예에서는 inner-shadow 및 fillStyle를 color+gradient+pattern으로 동시에 스타일 지정할 수 있습니다. 패턴 회전을 개별적으로 지정할 수 있습니다. 이제 얼룩말 줄무늬가 서로 직각을 이루고 있습니다. 경계 상자의 크기와 같은 클리핑 마스크가 사용되므로 초대형 컨테이너를 사용하여 잘라낸 도형을 닫을 필요가 없습니다. 이렇게 하면 불필요한 그림자 부분이 처리되지 않아 속도가 향상됩니다.
function innerShadow() {
function drawShape() { // draw anti-clockwise
ctx.arc(0, 0, 100, 0, Math.PI * 2, true); // Outer circle
ctx.moveTo(70, 0);
ctx.arc(0, 0, 70, 0, Math.PI, false); // Mouth
ctx.moveTo(-20, -20);
ctx.arc(30, -30, 10, 0, Math.PI * 2, false); // Left eye
ctx.moveTo(140, 70);
ctx.arc(-20, -30, 10, 0, Math.PI * 2, false); // Right eye
};
var width = 200;
var offset = width + 50;
var innerColor = "rgba(0,0,0,1)";
var outerColor = "rgba(0,0,0,1)";
ctx.translate(150, 170);
// apply inner-shadow
ctx.save();
ctx.fillStyle = "#000";
ctx.shadowColor = innerColor;
ctx.shadowBlur = getBlurValue(120);
ctx.shadowOffsetX = -15;
ctx.shadowOffsetY = 15;
// create clipping path (around blur + shape, preventing outer-rect blurring)
ctx.beginPath();
ctx.rect(-offset/2, -offset/2, offset, offset);
ctx.clip();
// apply inner-shadow (w/ clockwise vs. anti-clockwise cutout)
ctx.beginPath();
ctx.rect(-offset/2, -offset/2, offset, offset);
drawShape();
ctx.fill();
ctx.restore();
// cutout temporary rectangle used to create inner-shadow
ctx.globalCompositeOperation = "destination-out";
ctx.fill();
// prepare vector paths
ctx.beginPath();
drawShape();
// apply fill-gradient to inner-shadow
ctx.save();
ctx.globalCompositeOperation = "source-in";
var gradient = ctx.createLinearGradient(-offset/2, 0, offset/2, 0);
gradient.addColorStop(0.3, '#ff0');
gradient.addColorStop(0.7, '#f00');
ctx.fillStyle = gradient;
ctx.fill();
// apply fill-pattern to inner-shadow
ctx.globalCompositeOperation = "source-atop";
ctx.globalAlpha = 1;
ctx.rotate(0.9);
ctx.fillStyle = ctx.createPattern(image, 'repeat');
ctx.fill();
ctx.restore();
// apply fill-gradient
ctx.save();
ctx.globalCompositeOperation = "destination-over";
var gradient = ctx.createLinearGradient(-offset/2, -offset/2, offset/2, offset/2);
gradient.addColorStop(0.1, '#f00');
gradient.addColorStop(0.5, 'rgba(255,255,0,1)');
gradient.addColorStop(1.0, '#00f');
ctx.fillStyle = gradient
ctx.fill();
// apply fill-pattern
ctx.globalCompositeOperation = "source-atop";
ctx.globalAlpha = 0.2;
ctx.rotate(-0.4);
ctx.fillStyle = ctx.createPattern(image, 'repeat');
ctx.fill();
ctx.restore();
// apply outer-shadow (color-only without temporary layer)
ctx.globalCompositeOperation = "destination-over";
ctx.shadowColor = outerColor;
ctx.shadowBlur = 40;
ctx.shadowOffsetX = 15;
ctx.shadowOffsetY = 10;
ctx.fillStyle = "#fff";
ctx.fill();
};
이 예시에서 볼 수 있듯이 globalCompositeOperation을 사용하면 효과를 함께 체이닝하여 마스킹 및 블렌딩을 활용하여 더 정교한 효과를 만들 수 있습니다. 화면은 여러분의 것입니다. ;)
Spaceage: 생성형 효과
<canvas>
에서 유니코드 문자 0x2708부터 시작합니다.
다음과 같이 음영 처리된 예시로 변환됩니다.
…가는 선 너비 (0.25)로 ctx.strokeText()
를 여러 번 호출하고 x 오프셋과 알파를 천천히 줄여 벡터 요소에 모션 느낌을 줄 수 있습니다.
요소 XY 위치를 사인/코사인 웨이브에 매핑하고 HSL 속성을 사용하여 색상을 순환하면 다음과 같은 '생물학적 위험' 예와 같이 더 흥미로운 효과를 만들 수 있습니다.
HSL: 색조, 채도, 밝기 (1978)
HSL은 CSS3 사양에서 새로 지원되는 형식입니다. HEX는 컴퓨터용으로 설계되었지만 HSL은 사람이 읽을 수 있도록 설계되었습니다.
HSL의 편리성을 보여주는 예시입니다. 색상 스펙트럼을 순환하려면 '색조'를 360에서 늘리면 됩니다. 색조는 원통형 방식으로 스펙트럼에 매핑됩니다. 밝기는 색상의 어두움/밝음을 제어합니다. 0% 는 검은색 픽셀을 나타내고 100% 는 흰색 픽셀을 나타냅니다. 채도는 색상의 밝기 또는 선명도를 제어합니다. 회색은 채도가 0%로 생성되고 선명한 색상은 값 100%를 사용하여 생성됩니다.
HSL은 최신 표준이므로 색상 공간 변환을 통해 이전 브라우저를 계속 지원하는 것이 좋습니다. 다음 코드는 HSL 객체({ H: 360, S: 100, L: 100})를 허용하고 RGB 객체({ R: 255, G: 255, B: 255 })를 출력합니다. 그런 다음 이러한 값을 사용하여 rgb 또는 rgba 문자열을 만들 수 있습니다. 자세한 내용은 위키백과에서 HSL에 관한 유용한 도움말을 참고하세요.
// HSL (1978) = H: Hue / S: Saturation / L: Lightness
HSL_RGB = function (o) { // { H: 0-360, S: 0-100, L: 0-100 }
var H = o.H / 360,
S = o.S / 100,
L = o.L / 100,
R, G, B, _1, _2;
function Hue_2_RGB(v1, v2, vH) {
if (vH < 0) vH += 1;
if (vH > 1) vH -= 1;
if ((6 * vH) < 1) return v1 + (v2 - v1) * 6 * vH;
if ((2 * vH) < 1) return v2;
if ((3 * vH) < 2) return v1 + (v2 - v1) * ((2 / 3) - vH) * 6;
return v1;
}
if (S == 0) { // HSL from 0 to 1
R = L * 255;
G = L * 255;
B = L * 255;
} else {
if (L < 0.5) {
_2 = L * (1 + S);
} else {
_2 = (L + S) - (S * L);
}
_1 = 2 * L - _2;
R = 255 * Hue_2_RGB(_1, _2, H + (1 / 3));
G = 255 * Hue_2_RGB(_1, _2, H);
B = 255 * Hue_2_RGB(_1, _2, H - (1 / 3));
}
return {
R: R,
G: G,
B: B
};
};
requestAnimationFrame을 사용하여 애니메이션 만들기
이전에는 JavaScript로 애니메이션을 만들 때 setTimeout
와 setInterval
라는 두 가지 옵션이 있었습니다.
window.requestAnimationFrame
는 둘 다를 대체하는 새로운 표준입니다. 브라우저가 사용 가능한 리소스를 기반으로 애니메이션을 조절할 수 있도록 허용하여 전 세계 전기와 컴퓨터의 심박수를 절약합니다.
몇 가지 중요한 기능은 다음과 같습니다.
- 사용자가 프레임을 벗어나면 불필요한 리소스가 사용되지 않도록 애니메이션이 느려지거나 완전히 중지될 수 있습니다.
- 프레임 속도는 60FPS로 제한됩니다. 그 이유는 인간이 감지할 수 있는 수준을 훨씬 웃돌기 때문입니다 (대부분의 인간은 30FPS에서 애니메이션이 '부드럽게' 표시된다고 생각함).
이 글을 작성하는 시점에서 requestAnimationFrame
를 사용하려면 공급업체별 접두사가 필요합니다.
폴 아이리시님은 스마트 애니메이션을 위한 requestAnimationFrame에서 교차 판매자 지원이 있는 shim 레이어를 만들었습니다.
// shim layer with setTimeout fallback
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(/* function */ callback, /* DOMElement */ element){
window.setTimeout(callback, 1000 / 60);
};
})();
한 단계 더 나아가 야심 찬 개발자는 이 새로운 표준으로 전환하는 동시에 이전 브라우저를 더 많이 지원하는 requestAnimationFrame.js와 같은 대체 구현(해결해야 할 몇 가지 기능이 있음)을 함께 사용하고자 할 수 있습니다.
(function animate() {
var i = 50;
while(i--) {
if (n > endpos) return;
n += definition;
ctx.globalAlpha = (0.5 - (n + startpos) / endpos) * alpha;
if (doColorCycle) {
hue = n + color;
ctx.strokeStyle = "hsl(" + (hue % 360) + ",99%,50%)"; // iterate hue
}
var x = cos(n / cosdiv) * n * cosmult; // cosine
var y = sin(n / sindiv) * n * sinmult; // sin
ctx.strokeText(text, x + xoffset, y + yoffset); // draw rainbow text
}
timeout = window.requestAnimationFrame(animate, 0);
})();
소스 코드
여러 브라우저 공급업체의 지원을 통해 <canvas>
의 미래에 대한 의문은 없습니다. PhoneGap을 사용하여 iPhone/Android/데스크톱 실행 파일로 포팅할 수 있습니다.