내 배경
<canvas>은 2006년 Firefox v2.0이 출시되었을 때 알게 되었습니다. 변환 행렬을 설명하는 Ajaxian의 기사를 보고 영감을 받아 첫 번째 <canvas> 웹 앱인 Color Sphere (2007)를 만들었습니다. 이로 인해 색상과 그래픽 프리미티브의 세계에 몰입하게 되었고, 브라우저에서 '그림판보다 나은' 애플리케이션을 만들기 위해 Sketchpad (2007~2008년)를 만들게 되었습니다.
이러한 실험을 통해 결국 오랜 친구인 찰스 프리처드와 함께 스타트업 Mugtug를 만들게 되었습니다. HTML5 <canvas>로 Darkroom을 개발하고 있습니다. Darkroom은
비파괴적인 사진 공유 앱으로, 픽셀 기반 필터의 기능과 벡터 기반 타이포그래피 및 그리기를
결합합니다.
소개
<canvas>는 자바스크립트 프로그래머에게 화면의 색상, 벡터, 픽셀(모니터의 시각적 구성)을 완벽하게 제어할 수 있는 기능을 제공합니다.
다음 예시에서는 <canvas>에서 크게 주목받지 못한 영역인 텍스트 효과 만들기를 다룹니다. <canvas>에서 만들 수 있는 텍스트 효과는 상상할 수 있는 만큼 다양합니다. 이 데모에서는 가능한 것 중 일부를 다룹니다. 이 도움말에서는 '텍스트'를 다루지만, 이 방법은 모든 벡터 객체에 적용할 수 있습니다. 게임 및 기타 애플리케이션에서 흥미로운 시각적 요소를 만들 수 있습니다.
<canvas>의 텍스트 그림자<canvas>에서 클리핑 마스크를 만들고,<canvas>에서 측정항목을 찾고, 그림자 속성을 사용하는 CSS와 유사한 텍스트 효과- 네온 무지개, 얼룩말 반사 - 연쇄 효과
- 포토샵과 유사한 텍스트 효과를 globalCompositeOperation, createLinearGradient, createPattern을 사용하는
<canvas>가지 예시로 보여줍니다. <canvas>의 내부 및 외부 그림자- 잘 알려지지 않은 기능을 공개합니다. 시계 방향과 시계 반대 방향을 사용하여 드롭 섀도우의 반대인 내부 그림자를 만듭니다.
- 우주 시대 - 생성형 효과
<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" // string
- 그림자 색상입니다. RGB, RGBA, HSL, HEX, 기타 입력이 유효합니다.
- ctx.shadowOffsetX = 0; // integer
- 텍스트와 관련된 그림자의 가로 거리입니다.
- ctx.shadowOffsetY = 0; // integer
- 텍스트와 관련된 그림자의 세로 거리입니다.
- ctx.shadowBlur = 10; // integer
- 그림자에 흐리게 처리 효과를 적용합니다. 값이 클수록 흐리게 처리 효과가 커집니다.
먼저 <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>로 변환할 때 다음 두 가지 사항에 유의해야 합니다.
- 그림자 흐림 효과 (세 번째 값)가 없으므로 fillText가 동일한 결과를 생성하므로 실제로 그림자를 실행할 이유가 없습니다.
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에서 동일한 글꼴 속성을 가진 요소를 만들고 너비를 측정할 형식으로 설정하여 PT, PC, EM, EX, PX 등의 변환 비율을 찾을 수 있습니다. 예를 들어 EM -> PX 변환을 캡처하려면 'height: 1em'을 사용하여 DOM 요소를 측정합니다. 결과 offsetHeight는 각 EM에 있는 PX의 수입니다.
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가 처음부터 그려지지 않도록 할 수 있습니다 (그림자는 그려지도록 허용).
텍스트를 둘러싸는 클리핑 경로를 만들려면 텍스트의 높이 (역사적으로 인쇄기에서 문자 'M'의 높이인 'em-height'라고 함)와 텍스트의 너비를 알아야 합니다.
ctx.measureText().width를 사용하여 너비를 가져올 수 있지만 ctx.measureText().height는 존재하지 않습니다.
다행히 CSS 해킹 (CSS 측정을 사용하여 이전 <canvas> 구현을 수정하는 방법에 관한 자세한 내용은 서체 측정항목을 참고하세요)을 통해 동일한 글꼴 속성을 가진 <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> 명령어를 모두 수동으로 입력하고 싶지 않을 것이므로 데모 소스에 간단한 텍스트 그림자 파서를 포함했습니다. 이렇게 하면 CSS 명령어를 제공하고 <canvas> 명령어를 생성할 수 있습니다.
이제 <canvas> 요소에 연결할 수 있는 다양한 스타일이 있습니다.
이러한 그림자 효과는 웹 글꼴부터 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로 설정되어 더 세밀한 제어와 더 부드러운 가장자리를 지원하는 경우 이 기능은 흥미로운 기능입니다. Lighter는 다양한 용도로 사용되어 왔으며, 최근 제가 가장 좋아하는 것은 http://weavesilk.com/에서 찾을 수 있는 HTML5 데스크톱 배경 생성기입니다.
내 데모 중 하나인 Breathing Galaxies (JS1k)도 lighter 모드를 사용합니다. 이 두 예시의 패턴을 그리면 이 모드가 어떤 효과를 내는지 알 수 있습니다.
globalCompositeOperation 브라우저 처리
네온 무지개 지터 효과
다음 데모에서는 globalCompositeOperation (source-in, lighter, darker)을 사용하여 효과를 연결하여 지터링된 윤곽선이 있는 포토샵과 같은 네온 무지개 빛을 구현합니다.
이 데모는 '<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 (흰색), 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 및 fillStyle을 색상+그라데이션+패턴으로 동시에 스타일 지정할 수 있습니다. 패턴 회전을 개별적으로 지정할 수 있습니다. 이제 얼룩말 줄무늬가 서로 수직입니다. 경계 상자 크기의 클리핑 마스크를 사용하므로 잘라낸 모양을 둘러싸는 초대형 컨테이너가 필요하지 않습니다. 그림자의 불필요한 부분이 처리되지 않도록 하여 속도를 개선합니다.
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:
…음영 처리된 예시:
…은 얇은 lineWidth (0.25)로 ctx.strokeText()를 여러 번 호출하여 x-offset과 알파를 천천히 줄여 벡터 요소에 움직임을 부여함으로써 달성할 수 있습니다.
요소 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를 사용하려면 공급업체별 프리픽스가 필요합니다.
Paul Irish는 스마트 애니메이션을 위한 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/데스크톱 실행 파일로 이식할 수 있습니다.