Last active
June 25, 2018 00:22
-
-
Save obenjiro/52715dfae7ca16da9f09 to your computer and use it in GitHub Desktop.
Canvas Conical Gradient
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
body { | |
margin: 0; | |
padding: 0; | |
background-color: #fff; | |
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAANUlEQVQ4jWM8c+bMfwY8wNjYmBGfPBM+SWLAqAGDwQDG///xJgOGs2fP4lUw8F4YNYAKBgAA2NYKfxDn4ZUAAAAASUVORK5CYII=); | |
overflow: hidden; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<canvas id='c'></canvas> | |
<script type="text/javascript"> | |
document.addEventListener('DOMContentLoaded', function() { | |
var canvas = document.getElementById('c'); | |
canvas.width = canvas.height = 465; | |
var context = canvas.getContext('2d'); | |
var PI = Math.PI; | |
var TWO_PI = PI * 2; | |
// --------------------------------- | |
// RGB Wheel | |
// --------------------------------- | |
new ConicalGradient() | |
.addColorStop(0 , [255, 0, 0]) | |
.addColorStop(1 / 3, [0, 255, 0]) | |
.addColorStop(2 / 3, [0, 0, 255]) | |
.addColorStop(1 , [255, 0, 0]) | |
.fill(context, 132, 132, 75, 0, TWO_PI, false); | |
// --------------------------------- | |
// CMY Alpha Gradient | |
// --------------------------------- | |
new ConicalGradient() | |
.addColorStop(0 , [0, 255, 255, 0]) | |
.addColorStop(0.5, [255, 0, 255, 0.35]) | |
.addColorStop(1 , [255, 255, 0]) | |
.fill(context, 330, 132, 75, PI, PI * 3, true); | |
// --------------------------------- | |
// Metal Knob | |
// --------------------------------- | |
context.beginPath(); | |
context.arc(132, 330, 75, 0, TWO_PI, false); | |
context.save(); | |
context.fillStyle = context.shadowColor = 'black'; | |
context.shadowBlur = 10; | |
context.fill(); | |
context.restore(); | |
new ConicalGradient() | |
.addColorStop(0 , [100, 100, 100]) | |
.addColorStop(1 / 12, [255, 255, 255]) | |
.addColorStop(2 / 12, [255, 255, 255]) | |
.addColorStop(3 / 12, [200, 200, 200]) | |
.addColorStop(4 / 12, [255, 255, 255]) | |
.addColorStop(5 / 12, [255, 255, 255]) | |
.addColorStop(6 / 12, [100, 100, 100]) | |
.addColorStop(7 / 12, [255, 255, 255]) | |
.addColorStop(8 / 12, [255, 255, 255]) | |
.addColorStop(9 / 12, [200, 200, 200]) | |
.addColorStop(10 / 12, [255, 255, 255]) | |
.addColorStop(11 / 12, [255, 255, 255]) | |
.addColorStop(1 , [100, 100, 100]) | |
.fill(context, 132, 330, 72, PI / 6, PI * 2 + PI / 6, false) | |
.fill(context, 132, 330, 70, -PI / 6, PI * 2 - PI / 6, false); | |
var i, radius, startAngle; | |
context.beginPath(); | |
for (i = 0; i < 50; i++) { | |
radius = 72 * (i / 50); | |
startAngle = Math.random() * TWO_PI; | |
context.moveTo(132 + radius * Math.cos(startAngle), 330 + radius * Math.sin(startAngle)); | |
context.arc(132, 330, radius, startAngle, Math.random() * TWO_PI, false); | |
} | |
context.lineWidth = 0.3; | |
context.strokeStyle = 'rgba(255, 255, 255, .3)'; | |
context.stroke(); | |
context.beginPath(); | |
for (i = 0; i < 45; i++) { | |
radius = 72 * (i / 45); | |
startAngle = Math.random() * TWO_PI; | |
context.moveTo(132 + radius * Math.cos(startAngle), 330 + radius * Math.sin(startAngle)); | |
context.arc(132, 330, radius, startAngle, Math.random() * TWO_PI, false); | |
} | |
context.lineWidth = 0.3; | |
context.strokeStyle = 'rgba(0, 0, 0, .05)'; | |
context.stroke(); | |
context.beginPath(); | |
context.arc(72, 330, 4, 0, TWO_PI, false); | |
context.fillStyle = 'rgba(0, 0, 0, .75)'; | |
context.fill(); | |
// --------------------------------- | |
// Compact disc | |
// --------------------------------- | |
context.beginPath(); | |
context.arc(330, 330, 75, 0, Math.PI * 2, false); | |
context.save(); | |
context.fillStyle = context.shadowColor = 'black'; | |
context.shadowBlur = 10; | |
context.fill(); | |
context.restore(); | |
new ConicalGradient() | |
.addColorStop(0 , [255, 220, 220]) | |
.addColorStop(1 / 22, [255, 255, 220]) | |
.addColorStop(2 / 22, [220, 255, 220]) | |
.addColorStop(3 / 22, [220, 255, 255]) | |
.addColorStop(4 / 22, [220, 220, 255]) | |
.addColorStop(7 / 22, [255, 255, 255]) | |
.addColorStop(10 / 22, [220, 220, 225]) | |
.addColorStop(11 / 22, [255, 220, 220]) | |
.addColorStop(12 / 22, [255, 255, 220]) | |
.addColorStop(13 / 22, [220, 255, 220]) | |
.addColorStop(14 / 22, [220, 255, 255]) | |
.addColorStop(15 / 22, [220, 220, 255]) | |
.addColorStop(18 / 22, [255, 255, 255]) | |
.addColorStop(21 / 22, [220, 220, 225]) | |
.addColorStop(1 , [255, 220, 220]) | |
.fill(context, 330, 330, 75, -PI * 3 / 22, TWO_PI-PI * 3 / 22, false); | |
context.beginPath(); | |
context.arc(330, 330, 75, 0, Math.PI * 2, false); | |
context.lineWidth = 0.5; | |
context.strokeStyle = 'rgba(0, 0, 0, 0.5)'; | |
context.stroke(); | |
context.beginPath(); | |
context.arc(330, 330, 25, 0, Math.PI * 2, false); | |
context.globalCompositeOperation = 'destination-out'; | |
context.fill(); | |
context.globalCompositeOperation = 'source-over'; | |
context.stroke(); | |
context.beginPath(); | |
context.arc(330, 330, 30, 0, Math.PI * 2, false); | |
context.fillStyle = 'rgba(0, 0, 0, 0.15)'; | |
context.fill(); | |
context.beginPath(); | |
context.arc(330, 330, 10, 0, Math.PI * 2, false); | |
context.globalCompositeOperation = 'destination-out'; | |
context.fillStyle = 'black'; | |
context.fill(); | |
context.globalCompositeOperation = 'source-over'; | |
context.strokeStyle = 'rgba(0, 0, 0, 0.5)'; | |
context.stroke(); | |
}, false); | |
</script> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* ConicalGradient | |
*/ | |
function ConicalGradient() { | |
this._offsets = []; | |
this._colors = []; | |
} | |
ConicalGradient.prototype = { | |
/** | |
* addColorStop | |
* | |
* @param {Number} offset | |
* @param {Array} color RGBA 値を配列で指定, アルファ値は省略可 (ex) [255, 127, 0, 0.75] | |
*/ | |
addColorStop: function(offset, color) { | |
this._offsets.push(offset); | |
this._colors.push(color); | |
return this; | |
}, | |
/** | |
* _offsetsReverse (array.forEach callback) | |
*/ | |
_offsetsReverse: function(offset, index, array) { | |
array[index] = 1 - offset; | |
}, | |
/** | |
* fill | |
* | |
* グラデーションを描画する | |
* 第2引数以降は context.arc() とほぼ同じ | |
* | |
* @param {Number} context 対象となる context | |
* @param {Number} x | |
* @param {Number} y | |
* @param {Number} radius | |
* @param {Number} startAngle | |
* @param {Number} endAngle | |
* @param {Boolean} anticlockwise | |
*/ | |
fill: function(context, x, y, radius, startAngle, endAngle, anticlockwise) { | |
var offsets = this._offsets; | |
var colors = this._colors; | |
var PI = Math.PI; | |
var TWO_PI = PI * 2; | |
if (startAngle < 0) startAngle = startAngle % TWO_PI + TWO_PI; | |
startAngle %= TWO_PI; | |
if (endAngle < 0) endAngle = endAngle % TWO_PI + TWO_PI; | |
endAngle %= TWO_PI; | |
if (anticlockwise) { | |
// 反時計回り | |
var swap = startAngle; | |
startAngle = endAngle; | |
endAngle = swap; | |
colors.reverse(); | |
offsets.reverse(); | |
offsets.forEach(this._offsetsReverse); | |
} | |
if ( | |
startAngle > endAngle || | |
Math.abs(endAngle - startAngle) < 0.0001 // 誤差の範囲内なら同値とする | |
) endAngle += TWO_PI; | |
var colorsLength = colors.length; // 色数 | |
var currentColorIndex = 0; // 現在の色のインデックス | |
var currentColor = colors[currentColorIndex]; // 現在の色 | |
var nextColor = colors[currentColorIndex]; // 次の色 | |
var prevOffset = 0; // 前のオフセット値 | |
var currentOffset = offsets[currentColorIndex]; // 現在のオフセット値 | |
var offsetDist = currentOffset - prevOffset; // オフセットの差 | |
var totalAngleDeg = (endAngle - startAngle) * 180 / PI; // 塗る範囲の角度量 | |
var stepAngleRad = (endAngle - startAngle) / totalAngleDeg; // 一回の塗りの角度量 | |
var arcStartAngle = startAngle; // ループ内での塗りの開始角度 | |
var arcEndAngle; // ループ内での塗りの終了角度 | |
var r1 = currentColor[0], g1 = currentColor[1], b1 = currentColor[2], a1 = currentColor[3]; | |
var r2 = nextColor[0], g2 = nextColor[1], b2 = nextColor[2], a2 = nextColor[3]; | |
if (!a1 && a1 !== 0) a1 = 1; | |
if (!a2 && a2 !== 0) a2 = 1; | |
var rd = r2 - r1, gd = g2 - g1, bd = b2 - b1, ad = a2 - a1; | |
var t, r, g, b, a; | |
context.save(); | |
for (var i = 0, n = 1 / totalAngleDeg; i < 1; i += n) { | |
if (i >= currentOffset) { | |
// 次の色へ | |
currentColorIndex++; | |
currentColor = nextColor; | |
r1 = currentColor[0]; g1 = currentColor[1]; b1 = currentColor[2]; a1 = currentColor[3]; | |
if (!a1 && a1 !== 0) a1 = 1; | |
nextColor = colors[currentColorIndex]; | |
r2 = nextColor[0]; g2 = nextColor[1]; b2 = nextColor[2]; a2 = nextColor[3]; | |
if (!a2 && a2 !== 0) a2 = 1; | |
rd = r2 - r1; gd = g2 - g1; bd = b2 - b1; ad = a2 - a1; | |
prevOffset = currentOffset; | |
currentOffset = offsets[currentColorIndex]; | |
offsetDist = currentOffset - prevOffset; | |
} | |
t = (i - prevOffset) / offsetDist; | |
r = (rd * t + r1) & 255; | |
g = (gd * t + g1) & 255; | |
b = (bd * t + b1) & 255; | |
a = ad * t + a1; | |
arcEndAngle = arcStartAngle + stepAngleRad; | |
// 扇状に塗っていく | |
context.fillStyle = 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')'; | |
context.beginPath(); | |
context.moveTo(x, y); | |
context.arc(x, y, radius, arcStartAngle - 0.02, arcEndAngle, false); // モアレが出ないよう startAngle を少し手前から始める | |
context.closePath(); | |
context.fill(); | |
arcStartAngle += stepAngleRad; | |
} | |
context.restore(); | |
return this; | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment