Skip to content

Instantly share code, notes, and snippets.

@obenjiro
Last active June 25, 2018 00:22
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save obenjiro/52715dfae7ca16da9f09 to your computer and use it in GitHub Desktop.
Save obenjiro/52715dfae7ca16da9f09 to your computer and use it in GitHub Desktop.
Canvas Conical Gradient
body {
margin: 0;
padding: 0;
background-color: #fff;
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAANUlEQVQ4jWM8c+bMfwY8wNjYmBGfPBM+SWLAqAGDwQDG///xJgOGs2fP4lUw8F4YNYAKBgAA2NYKfxDn4ZUAAAAASUVORK5CYII=);
overflow: hidden;
}
<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>
/**
* 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