내 배경
2006년에 Firefox v2.0이 출시되었을 때 <canvas>
에 대해 인식하게 되었습니다. 변환 매트릭스를 설명하는 Ajaxian 관련 도움말을 보고 저의 첫 번째 <canvas>
웹 앱인 Color Sphere (2007)를 만들었습니다. 그 결과 색상의 세계에 몰입하게 되었고 그래픽 프리미티브에 Sketchpad (2007-2008)를 만들었습니다. 이는 브라우저에서 '그림판'보다 낫습니다.
이러한 실험을 통해 오랜 친구인 Charles Pritchard와 함께
Mugtug라는 스타트업이 설립되었습니다. Google에서는 HTML5 <canvas>
로 암실을 개발 중입니다. 암실은 비파괴 사진 공유 앱으로, 픽셀 기반 필터와 벡터 기반 서체 및 그림의 이점을 결합합니다.
소개
<canvas>
를 사용하면 자바스크립트 프로그래머가 화면의 색상, 벡터, 픽셀, 즉 모니터의 시각적 구성을 완전히 제어할 수 있습니다.
다음 예에서는 <canvas>
에서 그다지 주목받지 못한 한 영역, 즉 텍스트 효과를 만듭니다. <canvas>
에서 만들 수 있는 다양한 텍스트 효과는 상상할 수 있는 만큼 방대합니다. 이 데모에서는 가능성의 하위 섹션을 다룹니다. 이 문서에서는 '텍스트'를 다루지만 이 메서드는 모든 벡터 객체에 적용할 수 있습니다. 즉, 게임과 기타 애플리케이션에서 흥미로운 시각 효과를 만들 수 있습니다.
<canvas>
의 텍스트 그림자<canvas>
의 CSS와 유사한 텍스트 효과: 클리핑 마스크를 만들고<canvas>
에서 측정항목을 찾고 그림자 속성을 사용합니다.- 네온 무지개, 얼룩말 반사 - 연쇄 효과
- globalCompositeOperation, createLinearGradient, createPattern을 사용하는
<canvas>
예시에서 Photoshop과 유사한 텍스트 효과입니다. <canvas>
의 내부 및 외부 그림자- 약간 알려진 특징을 보여줍니다. 시계 방향과 반시계 방향 와인딩을 사용하여 그림자 (내부 그림자)의 역을 만듭니다.
- Spaceage - 생성 효과.
- hsl() 색상 순환과
window.requestAnimationFrame
를 사용하여<canvas>
의 생성형 기반 텍스트 효과로 움직임의 느낌을 줍니다.
캔버스의 텍스트 그림자
CSS3 사양에서 테두리 반경, 웹 그라데이션 등과 함께 가장 마음에 드는 추가 기능 중 하나는
그림자를 만드는 기능입니다. 특히 다음과 같이 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 = "빨간색" // 문자열
- 그림자의 색상: 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>
로 변환할 때 다음 두 가지 사항에 유의하세요.
- 그림자 블러 (세 번째 값)가 없으므로 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 및
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>
의 이전 구현을 수정하는 더 많은 방법은 타이포그래피 측정항목 참고) 동일한 글꼴 속성으로 <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>
요소에는 연결할 수 있는 다양한 스타일이 있습니다.
동일한 그림자 효과를 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>
더 어두운 GCO가 12개 있고 밝은 작업은 레이어 혼합 모드로 생각할 수 있습니다. 다른 10개 작업은 레이어에 알파 마스크로 적용됩니다 (한 레이어는 다른 레이어의 픽셀을 삭제함). globalCompositeOperation은 '레이어'(여기서는 코드 문자열)를 연결하여 새롭고 흥미로운 방식으로 결합합니다.
globalCompositeOperation 차트에서는 작동 중인 GCO 모드를 보여줍니다. 이 차트에서는 예상되는 결과를 자세히 보기 위해 많은 부분의 색상 스펙트럼과 여러 수준의 알파 투명도를 사용합니다. Mozilla 의 globalCompositeOperation 참조에서 텍스트 설명을 확인해 보시기 바랍니다. 자세한 내용은 포터 더프의 디지털 이미지 합성에서 이 작업이 어떻게 작동하는지 알아보세요.
제가 가장 좋아하는 모드는 globalCompositeOperation="lighter"입니다. 밝은 테마는 빛이 혼합되는 방식과 유사하게 추가된 픽셀을 혼합합니다. 빨간색, 녹색, 흰색 빛이 최대 강도일 때는 백색광이 보입니다. 특히 <canvas>
가 낮은 글로벌 알파로 설정된 경우 이 기능을 사용하면 더 세밀하게 제어할 수 있고 더 부드러운 가장자리를 사용할 수 있습니다. 라이터는 여러 용도로 사용되었는데, 최근에는 http://weavesilk.com/에서
HTML5 데스크톱 배경화면을 제작하는 것을 좋아했습니다.
제 데모 중 하나인 Breathing Galaxies (JS1k)에서도 더 밝은 모드를 사용합니다. 이 두 가지 예에서 패턴을 그리면 이 모드가 어떤 영향을 미치는지 확인할 수 있습니다.
globalCompositeOperation 브라우저 처리.
네온-무지개 잡음 효과
다음 데모에서는 globalCompositeOperation (소스-인, 라이트, 더 어둡게)을 사용하여 효과를 함께 연결하여 잡음이 있는 윤곽선의 포토샵과 유사한 네온 무지개 발광 효과를 구현합니다.
이 데모는 '<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의 최근 게시물에서 제안한 것처럼 시계 방향과 반시계 방향 와인딩 규칙의 고유한 속성을 사용하여 내부 그림자를 만들 수 있습니다. 이렇게 하려면 컨테이너 직사각형을 그려 '내부 그림자'를 만든 후 반대 권선 규칙을 사용하여 컷아웃 도형을 그려서 도형의 역을 만듭니다.
다음 예에서는 내부 그림자와 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부터 다음을 실행합니다.
음영 처리된 다음 예시로 바꾸겠습니다.
x-오프셋과 알파를 천천히 줄이면서 얇은 lineWidth (0.25)로 ctx.strokeText()
를 여러 번 호출하여 벡터 요소에 움직임을 줄 수 있습니다.
요소 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에 대한 Wikipedia의 유용한 문서를 참조하세요.
// 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으로 애니메이션 만들기
이전에는 setTimeout
와 setInterval
, 이렇게 두 가지 방법으로 JavaScript로 애니메이션을 만들 수 있었습니다.
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);
})();
소스 코드
브라우저 공급업체 영역 전반에서 지원되므로 PhoneGap을 사용하여 iPhone/Android/데스크톱 실행 파일로 포팅할 수 있는 <canvas>
의 미래에 대해서는 의문할 필요가 없습니다.
티타늄.