Skip to content

Instantly share code, notes, and snippets.

@achadidi
Created February 14, 2020 20:23
Show Gist options
  • Save achadidi/3f213927ec5d6f843dee37a861d4d2f5 to your computer and use it in GitHub Desktop.
Save achadidi/3f213927ec5d6f843dee37a861d4d2f5 to your computer and use it in GitHub Desktop.
Mandala Canvas JS (Live)
<canvas id="canvas" background="transparent"></canvas>
<canvas id="guide" background="transparent"></canvas>
<input class="sidebar_toggle-input" id="ToggleSidebar" type="checkbox" role="button" />
<label class="sidebar_toggle noselect" for="ToggleSidebar">
<span>≡</span>
</label>
<div class="sidebar">
<ul class="sidebar_tools">
<li>
<input class="sidebar_color" id="color" type="color" value="#000000">
</li>
<li class="sidebar_blendmodes">
<input class="blendmode-toggle" type="checkbox" id="blendmode" value="blendmode">
<label class="sidebar_label noselect" for="blendmode" id="blendmodelabel">Blend Mode</label>
<div class="sidebar_blendmodes-options">
<div class="sidebar_blend-option noselect" data-blend="normal" selected>Normal</div>
<div class="sidebar_blend-option noselect" data-blend="multiply">Multiply</div>
<div class="sidebar_blend-option noselect" data-blend="screen">Screen</div>
<div class="sidebar_blend-option noselect" data-blend="overlay">Overlay</div>
<div class="sidebar_blend-option noselect" data-blend="darken">Darken</div>
<div class="sidebar_blend-option noselect" data-blend="lighten">Lighten</div>
<div class="sidebar_blend-option noselect" data-blend="color">Color</div>
<div class="sidebar_blend-option noselect" data-blend="hard">Hard-Light</div>
<div class="sidebar_blend-option noselect" data-blend="soft">Soft-Light</div>
<div class="sidebar_blend-option noselect" data-blend="difference">Difference</div>
<div class="sidebar_blend-option noselect" data-blend="exclusion">Exclusion</div>
<div class="sidebar_blend-option noselect" data-blend="hue">Hue</div>
<div class="sidebar_blend-option noselect" data-blend="saturation">Saturation</div>
<div class="sidebar_blend-option noselect" data-blend="luminosity">Luminosity</div>
</div>
</li>
<li>
<input type="radio" id="random" name="selector" value="random" checked="checked">
<label class="sidebar_label noselect" for="random">Random Color</label>
</li>
<li>
<input type="radio" id="draw" name="selector" value="draw">
<label class="sidebar_label noselect" for="draw">Pencil</label>
</li>
<li>
<input type="radio" id="erase" name="selector" value="erase">
<label class="sidebar_label noselect" for="erase">Erase</label>
</li>
<li class="noborder">
<span class="sidebar_span noselect">Segments</span>
</li>
<li class="collapse">
<input id="Segments" type="range" class="sidebar_range" data-num="16" min="0" max="40" step="2" value="16">
</li>
<li class="noborder">
<span class="sidebar_span noselect">Stroke</span>
</li>
<li class="collapse">
<input id="Stroke" type="range" class="sidebar_range" data-num="15" min="1" max="40" step="1" value="15">
</li>
<li class="noborder">
<span class="sidebar_span noselect">Opacity</span>
</li>
<li class="collapse">
<input id="Opacity" type="range" class="sidebar_range" data-num="100" min="0" max="100" step="1" value="100">
</li>
<li>
<input type="checkbox" id="kaleido" name="field" value="kaleido" checked="checked">
<label class="sidebar_label noselect" for="kaleido">Kaleido</label>
</li>
<li class="noborder">
<div class="sidebar_palette-nav">
<div><span class="sidebar_span noselect">Palette</span></div>
<div>
<label id="PaletteSelectLeft" class="sidebar_palette-control sidebar_label noselect">&lt;</label>
<label id="PaletteSelectRight" class="sidebar_palette-control sidebar_label noselect">&gt;</label>
</div>
</div>
</li>
<li>
<div class="sidebar_palette" id="ColorPalette">
<div data-color="191E34" class="sidebar_palette_color noselect">
<label></label>
</div>
<div data-color="0D4C57" class="sidebar_palette_color noselect">
<label></label>
</div>
<div data-color="00797D" class="sidebar_palette_color noselect">
<label></label>
</div>
<div data-color="41A48D" class="sidebar_palette_color noselect">
<label></label>
</div>
</div>
</li>
<li>
<a id="download" download="kaleido-jl.png" class="sidebar_button noselect">Download</a>
</li>
<li>
<input id="clear" type="button" value="Clear" class="sidebar_button-danger">
</li>
</ul>
</div>
console.clear();
/*
Canvas kaleidoscope drawing. Supports mouse, touch and pen.
Also supports pressure sensitivity on browsers with Point Events (eg using the Surface/Wacom pen in Microsoft Edge)
*/
var kaleido = true;
var randstroke = true;
var alphaopacity = 1;
var globaloperation = "source-over";
var oldGlobaloperation;
var color;
var curpalette = 0;
var width = window.innerWidth;
var height = window.innerHeight;
const canvas = document.getElementById("canvas");
const guide = document.getElementById("guide");
const ctx = canvas.getContext("2d");
const guidectx = guide.getContext("2d");
canvas.width = width;
canvas.height = height;
guide.width = width;
guide.height = height;
ctx.strokeStyle = 'orange';
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.globalCompositeOperation = 'difference';
let isDrawing = false;
let lastX = 0;
let lastY = 0;
//let hue = 0;
let lineWidth = 20;
var countingUp = 1;
var hue = Math.floor(Math.random() * 360);
var hexcolor;
/* Options */
/* Ranges */
var slide = document.getElementById('Segments');
var segments = slide.value;
var stroke = document.getElementById('Stroke');
var strokemultiplier = stroke.value;
var opacity = document.getElementById('Opacity');
slide.onchange = function() {
slide.setAttribute("data-num", this.value);
segments = this.value;
drawGuide(guidectx);
}
stroke.onchange = function() {
stroke.setAttribute("data-num", this.value);
strokemultiplier = this.value;
}
opacity.onchange = function() {
opacity.setAttribute("data-num", this.value);
alphaopacity = this.value * .01;
ctx.globalAlpha = alphaopacity;
}
/* Draw Modes */
document.querySelector('#draw').onclick = function() {
randstroke = false;
ctx.globalCompositeOperation = oldGlobaloperation;
};
document.querySelector('#erase').onclick = function() {
randstroke = false;
ctx.globalCompositeOperation = 'destination-out';
};
document.querySelector('#random').onclick = function() {
randstroke = true;
ctx.globalCompositeOperation = oldGlobaloperation;
};
document.querySelector('#kaleido').onclick = function() {
kaleido = this.checked;
};
/* Color Select */
color = document.getElementById("color").value;
document.querySelector('#color').onchange = function() {
color = document.getElementById("color").value;
document.getElementById("draw").checked = true;
randstroke = false;
};
/* Clear */
const clearButton = document.querySelector('#clear');
function clear() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
clearButton.addEventListener('click', clear);
/* Download */
function download() {
var dt = canvas.toDataURL("image/png");
this.href = dt; //this may not work in the future.
}
function download_image(){
// Dump the canvas contents to a file.
var newcanvas = document.getElementById("canvas");
newcanvas.toBlob(function(blob) {
saveAs(blob, "output.png");
}, "image/png");
};
document.getElementById('download').addEventListener('click', download_image, false);
/* End Download */
/* Blend Modes */
document.getElementById("blendmodelabel").innerHTML = ctx.globalCompositeOperation;
oldGlobaloperation = ctx.globalCompositeOperation;
var blndopts = document.getElementsByClassName('sidebar_blend-option');
for (var i = 0; i < blndopts.length; ++i) {
var item = blndopts[i].onclick = function() {
var blendOption = this.getAttribute('data-blend');
ctx.globalCompositeOperation = blendOption;
oldGlobaloperation = blendOption;
document.getElementById("blendmode").checked = false;
document.getElementById("blendmodelabel").innerHTML = blendOption;
};
}
/* Load Palettes */
var colorPalletes = `{
"sets":[
{"one": "#1547ff", "two": "#1b7eff", "three": "#57a0ff", "four": "#bed8fd"},
{"one": "#191E34", "two": "#0D4C57", "three": "#00797D", "four": "#41A48D"},
{"one": "#FF1FA5", "two": "#B111FF", "three": "#10DAFF", "four": "#09FFD7"},
{"one": "#1D2939", "two": "#1CAF9A", "three": "#EE6456", "four": "#D1DC48"},
{"one": "#16204A", "two": "#FF4F53", "three": "#FF8F45", "four": "#00B29B"},
{"one": "#FF005F", "two": "#002C57", "three": "#7A00FF", "four": "#00FFD1"}
]
}`;
var objPalette = JSON.parse(colorPalletes);
var pltclrs = document.getElementsByClassName('sidebar_palette_color');
applyPalette();
function applyPalette() {
pltclrs[0].style.backgroundColor = objPalette.sets[curpalette].one;
pltclrs[0].setAttribute("data-color", objPalette.sets[curpalette].one);
pltclrs[1].style.backgroundColor = objPalette.sets[curpalette].two;
pltclrs[1].setAttribute("data-color", objPalette.sets[curpalette].two);
pltclrs[2].style.backgroundColor = objPalette.sets[curpalette].three;
pltclrs[2].setAttribute("data-color", objPalette.sets[curpalette].three);
pltclrs[3].style.backgroundColor = objPalette.sets[curpalette].four;
pltclrs[3].setAttribute("data-color", objPalette.sets[curpalette].four);
}
/* Palette Events */
var clrs = document.getElementsByClassName('sidebar_palette_color');
for (var i = 0; i < clrs.length; ++i) {
var item = clrs[i].onclick = function() {
var selectedColor = this.getAttribute('data-color');
document.getElementById('color').value = selectedColor;
document.getElementById('draw').checked = true;
randstroke = false;
color = selectedColor;
};
}
document.querySelector('#PaletteSelectLeft').onclick = function() {
curpalette = curpalette > 0 ? curpalette - 1 : objPalette.sets.length - 1;
applyPalette();
};
document.querySelector('#PaletteSelectRight').onclick = function() {
curpalette = curpalette < objPalette.sets.length - 1 ? curpalette + 1 : 0;
applyPalette();
};
document.querySelector('.sidebar_toggle').onclick = function(e) {
e.stopPropagation();
e.preventDefault();
document.getElementById('ToggleSidebar').checked = !document.getElementById('ToggleSidebar').checked;
};
/* End Options */
function drawGuide(ctxguide) {
ctxguide.clearRect(0, 0, width, height);
ctxguide.lineWidth = 1;
ctxguide.strokeStyle = "rgba(0,0,0,0.05)";
ctxguide.lineCap = "round";
ctxguide.save();
ctxguide.beginPath();
ctxguide.translate(width / 2, height / 2);
var r = 360 / segments * Math.PI / 180;
for (var i = 0; i < segments; ++i) {
ctxguide.rotate(r);
ctxguide.moveTo(0, 0);
ctxguide.lineTo(0, Math.max(width, height) * -1);
}
ctxguide.stroke();
ctxguide.restore();
}
function draw(e) {
if (!isDrawing) {
return;
}
ctx.strokeStyle = randstroke ? getColor() : color;
ctx.lineWidth = strokemultiplier;
segments = segments > 0 ? segments : 1;
var r = 360 / segments * Math.PI / 180;
for (var i = 0; i < segments; ++i) {
ctx.save();
ctx.translate(width / 2, height / 2);
ctx.rotate(r * i);
if (kaleido) {
if ((segments % 2 === 0) && i > 0 && i % 2 !== 0) {
ctx.scale(1, -1);
if (segments % 4 === 0) {
ctx.rotate((r));
}
}
}
ctx.beginPath();
ctx.moveTo(lastX - width / 2, lastY - height / 2);
if (e.type == "touchmove") {
ctx.lineTo(e.touches[0].clientX - width / 2, e.touches[0].clientY - height / 2);
} else {
ctx.lineTo(e.clientX - width / 2, e.clientY - height / 2);
}
ctx.stroke();
ctx.restore();
}
if (e.type == "touchmove") {
[lastX, lastY] = [e.touches[0].clientX, e.touches[0].clientY];
} else {
[lastX, lastY] = [e.clientX, e.clientY];
}
}
/*function getColor() {
hue++;
if ( hue == 360 ) {
hue = 0;
}
return 'hsl(' + hue +', 100%, 50%)';
}*/
function getColor() {
hue += countingUp;
if (hue >= 360 || hue <= 0)
{
countingUp *= -1;
}
var rgb = hslToRgb(parseInt(hue) / 360, 1, .5);
hexcolor = rgbToHex(rgb[0], rgb[1], rgb[2]);
return 'rgb(' + rgb[0] +',' + rgb[1] +',' + rgb[2] +')';
}
function hslToRgb(h, s, l){
var r, g, b;
if(s == 0){
r = g = b = l; // achromatic
}else{
var hue2rgb = function hue2rgb(p, q, t){
if(t < 0) t += 1;
if(t > 1) t -= 1;
if(t < 1/6) return p + (q - p) * 6 * t;
if(t < 1/2) return q;
if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
}
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3);
}
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}
function rgbToHex(r, g, b) {
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
}
function getlineWidth() {
/* lineWidth++;
if (lineWidth > 100) {
lineWidth = 1;
} */
return lineWidth;
}
/* Drawing */
if ("PointerEvent" in window) {
canvas.addEventListener("pointerdown", onDown);
window.addEventListener("pointermove", draw);
window.addEventListener("pointerup", onUp);
} else if ("TouchEvent" in window) {
canvas.addEventListener("touchstart", onDown);
window.addEventListener("touchmove", draw);
window.addEventListener("touchend", onUp);
}
canvas.addEventListener("mousedown", onDown);
window.addEventListener('mousemove', draw);
window.addEventListener('mouseup', onUp);
//window.addEventListener('mouseout', () => isDrawing = false );
drawGuide(guidectx);
function onDown(e) {
e.stopPropagation();
e.preventDefault();
isDrawing = true;
if (e.type == "touchstart") {
[lastX, lastY] = [e.touches[0].clientX, e.touches[0].clientY];
} else {
[lastX, lastY] = [e.clientX, e.clientY];
}
draw(e);
}
function onUp(e) {
document.getElementById('color').value = randstroke ? hexcolor : color;
isDrawing = false;
}
<script src="https://rawgit.com/eligrey/FileSaver.js/master/FileSaver.js"></script>
<script src="https://rawgit.com/eligrey/canvas-toBlob.js/master/canvas-toBlob.js"></script>
/* Colors */
$colorPrimaryA:#ffb88c;
$colorPrimaryB:#e56590;
$colorSecondaryA: #4ccbab;
$colorSecondaryB: #3e75c8;
$colorLight:#b6b6b7;
$colorLighter:#d8d8d8;
$colorDark:#4a4a4a;
$colorDarker:#191f28;
$colorBack:#f9f9f9;
$colorBorder:#f0f0f0;
$colorDanger: #e53935;
/* $colorDarker:#f9f9f9;
$colorBack:#191f28;
$colorBorder:#4a4a4a;
$colorDark:#d8d8d8; */
/* Sizes */
$size-sidebar: 16rem !default;
$size-base: 4rem !default;
$size-md: 3rem !default;
$size-sm: 2rem !default;
$size-xs: 1rem !default;
/* Font */
$font-size-base: 1rem !default; // Assumes the browser default, typically `16px`
$font-size-lg: 1.5rem !default;
$font-size-md: 1.25rem !default;
$font-size-sm: .875rem !default;
$font-size-xs: .75rem !default;
$light: 300;
$regular: 400;
$medium: 500;
$semi-bold: 600;
$bold: 700;
/* Font */
$family: 'Raleway', sans-serif;
html, body, canvas {
width: 100%;
height: 100%;
margin: 0;
overflow: hidden;
touch-action: none;
padding: 0;
}
#guide {
pointer-events: none;
}
#canvas {
cursor: crosshair;
position: absolute;
top: 0;
left: 0;
}
/* Sidebar */
.sidebar {
position: absolute;
top: 0;
left: 0;
height: 100%;
margin: 0;
width: $size-sidebar;
transition: all .5s ease;
overflow: hidden;
border-right: 1px solid $colorBorder;
input[type=radio], input[type=checkbox]{
display: none;
}
&_tools {
padding: 0;
margin: 0;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: -0;
overflow-y: auto;
overflow-x: hidden;
height: 100%;
background-color: $colorBack;
> li {
display: block;
height:$size-sm;
/* &.collapse{
height:0;
opacity:0;
transition:1s all ease;
&:hover{
height:$size-sm;
opacity:1;
}
}
&.noborder:hover + .collapse{
height:$size-sm;
opacity:1;
}
&.noborder:hover{
border-bottom: 0 solid $colorBorder;
} */
&:not(.noborder){
border-bottom: 1px solid $colorBorder;
}
}
.sidebar_blendmodes{
height:auto;
opacity:1;
//border-left: 4px solid $colorPrimaryA;
.sidebar_label{
border-left: 4px solid $colorPrimaryB;
}
}
}
&_label, &_button, &_span, &_blend-option {
padding: 0 calc(#{$size-xs} / 2);
padding-right:0;
display: block;
color: $colorLight;
text-align: left;
font-size: $font-size-xs;
line-height:$size-sm;
font-weight: normal;
border: 0;
transition: all .25s ease;
font-weight:$bold;
//text-transform: uppercase;
//letter-spacing: 1px;
text-transform:capitalize;
border-left: 4px solid $colorBack;
}
&_blend-option{
line-height:$font-size-lg;
&:hover{
cursor: pointer;
color: $colorDarker;
border-left: 4px solid $colorPrimaryA;
}
}
&_blendmodes-options{
height:0;
overflow:hidden;
transition:.5s all ease;
}
.blendmode-toggle:checked{
border:1px solid red;
}
.blendmode-toggle:checked ~ .sidebar_blendmodes-options{
height:calc(#{$font-size-lg} * 16);
}
.blendmode-toggle:checked > .sidebar_label{
border-left: 4px solid $colorDanger;
}
&_label:hover, &_button:hover {
cursor: pointer;
border-left: 4px solid $colorPrimaryA;
}
&_color {
-webkit-appearance: none;
display: block;
outline: none;
border: 0;
width: calc(100% - #{$size-md});
height: $size-sm;
background-color: transparent;
cursor: pointer;
margin-left: $size-md;
right: 0;
padding: 0;
}
input[type="radio"]:checked ~ .sidebar_label {
//background-color: $colorPrimary;
color: $colorDarker;
border-left: 4px solid $colorSecondaryA;
}
input[type="checkbox"]:checked ~ .sidebar_label {
//background-color: $colorPrimary;
color: $colorDarker;
border-left: 4px solid $colorSecondaryB;
}
&_button, &_button-danger {
-webkit-appearance: none;
display: block;
width: 100%;
outline: none;
border: 0;
text-align: left;
font-family:$family;
font-size: $font-size-xs;
line-height: $size-sm;
padding: 0 calc(#{$size-xs} / 2);
color: $colorLight;
font-weight:$bold;
background-color: transparent;
transition: all .25s ease;
//letter-spacing: 1px;
//text-transform: uppercase;
border-left: 4px solid $colorBack;
}
&_button-danger {
color: $colorLight;
&:hover {
border-left: 4px solid $colorDanger;
background-color: $colorDanger;
color: $colorBack;
cursor: pointer;
}
}
&_palette-nav{
width: 100%;
div{
display: inline-block;
width: 55%;
}
div:last-child{
display: inline-block;
width: 45%;
float: right;
}
.sidebar_palette-control{
&:hover{
border:0;
color:$colorDarker;
}
}
}
&_palette-control{
display: inline-block;
width: 50%;
padding: 0;
margin: 0;
float: left;
text-align: center;
border: 0;
}
&_palette {
padding: 0;
magin: 0;
width: 100%;
text-align: center;
.sidebar_palette_color {
width: 25%;
height: $size-sm;
margin: 0;
vertical-align: middle;
font-size: 8pt;
cursor: pointer;
display:inline-block;
padding: 0;
magin: 0;
float:left;
}
}
}
/* End Sidebar */
/* Toggle Button */
.sidebar_toggle-input {
+ .sidebar_toggle {
position: absolute;
top: 0;
left: 0;
z-index: 1;
background-color: $colorBack;
//padding: 8px;
text-transform: uppercase;
font-size: $font-size-md;
height: $size-sm;
line-height: $size-sm;
letter-spacing: 1px;
width: $size-md;
text-align: center;
//border: 1px solid transparent;
color: $colorDarker;
cursor: pointer;
transition: all .5s ease;
box-sizing:border-box;
}
&[type=checkbox] {
display: none;
}
&:checked {
~ .sidebar {
left: $size-sidebar * -1;
}
+ .sidebar_toggle {
left: 0;
//border: 1px solid $colorLight;
background-color: rgba(255, 255, 255, .9);
color: #000000;
}
}
}
/* End Toggle Button */
/* Ranges Sliders */
input[type=range] {
/*removes default webkit styles*/
-webkit-appearance: none;
/*fix for FF unable to apply focus style bug */
border: 1px solid $colorBorder;
/*required for proper track sizing in FF*/
width: calc(100% - #{$size-sm});
left: $size-xs;
margin: 10px auto;
margin-top: 0;
&::-webkit-slider-runnable-track {
width: calc(100% - 0px);
height: 20px;
background: $colorBack;
border: none;
border-radius: 0;
}
&::-webkit-slider-thumb {
-webkit-appearance: none;
border: none;
height: 20px;
width: 10px;
border-radius: 0;
background: $colorPrimaryA;
}
&:focus {
outline: none;
&::-webkit-slider-runnable-track {
background: $colorBorder;
}
&::-webkit-slider-thumb {
background: $colorPrimaryB;
}
}
}
.sidebar_range {
position: relative;
&:after {
content: attr(data-num);
position: absolute;
display: inline-block;
top: -22px;
right: 0px;
color: #9E9E9E;
font-size: 9pt;
line-height: 8pt;
}
}
/* End Ranges */
/* Helpers */
.noselect {
user-select: none;
}
.noborder:hover + .collapse{
height:$size-sm;
}
.sidebar_tools .collapse:hover{
height:$size-sm;
}
.sidebar_tools .collapse{
height:$size-sm;
}
.last-li {
bottom: 0;
position: absolute;
left: 0px;
right: 1px;
border-top: 1px solid $colorLight;
border-bottom: 0;
}
/* End Helpers */
/*Scrollbar*/
@mixin scrollbars($size, $mc, $fc: mix($mc, black, 60%), $bc: mix($mc, white, 100%)) {
::-webkit-scrollbar {
width: $size;
height: $size;
}
::-webkit-scrollbar-thumb {
background: $fc;
}
::-webkit-scrollbar-track {
background: $bc;
}
// For Internet Explorer
body {
scrollbar-face-color: $fc;
scrollbar-track-color: $bc;
}
}
@include scrollbars(calc(#{$size-xs} / 2), $colorLighter);
/* End Scrollbar */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment