├── demo ├── guidelines.png ├── symmetry.js └── index.html ├── README.md ├── LICENSE └── symmetry.js /demo/guidelines.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianjs/Mandala-JS/HEAD/demo/guidelines.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](https://www.independence.dev/img/mandala2.gif) 2 | 3 | # Create mandalas in JS 4 | 5 | Mandala is the Open Source version of [JustMandala.com](http://justmandala.com/) - #3 Product of the day on [ProductHunt](https://www.producthunt.com/posts/justmandala) 6 | 7 | It's written in Vanilla JS using Canvas. 8 | 9 | ## How to ? 10 | 11 | Simply Fork or Download all the files, (or Remix it on Glitch, it's even easier) and you are good to go! 12 | 13 | ### Canvas 14 | 15 | You need to add a fixed width and height to your canvas. 16 | 17 | ```html 18 | 19 | ``` 20 | 21 | ### Demo 22 | 23 | check ./demo if you want to see a working demo of this JS library 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Florian ARGAUD 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /demo/symmetry.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2020 - Florian ARGAUD */ 2 | 3 | // radian = Nombre de points souhaité * 2 4 | var canvas = document.getElementById("drawCanvas"); 5 | 6 | var style = canvas.style; 7 | 8 | var getcanvas, ctx, prevX, prevY; 9 | let symmetry = canvas.width; 10 | let xCenter = symmetry / 2; 11 | let radian = 8; 12 | let brushSize = 4; 13 | let color = "#1A202C"; 14 | let brushClick = 0; 15 | 16 | // colors 17 | 18 | addEventListener("load", load); 19 | document.getElementById("red").addEventListener("click", () => { 20 | color = "#FC8181"; 21 | }); 22 | 23 | document.getElementById("blue").addEventListener("click", () => { 24 | color = "#63B3ED"; 25 | }); 26 | document.getElementById("green").addEventListener("click", () => { 27 | color = "#4FD1C5"; 28 | }); 29 | document.getElementById("darkgray").addEventListener("click", () => { 30 | color = "#1A202C"; 31 | }); 32 | document.getElementById("purple").addEventListener("click", () => { 33 | color = "#9F7AEA"; 34 | }); 35 | document.getElementById("eraser").addEventListener("click", () => { 36 | color = "#fff"; 37 | }); 38 | 39 | document.getElementById("brush").addEventListener("click", () => { 40 | console.log(brushClick); 41 | if (brushClick == 1) { 42 | brushSize = 8; 43 | brushClick = 2; 44 | } else if (brushClick == 2) { 45 | brushSize = 12; 46 | brushClick = 3; 47 | } else if (brushClick == 3) { 48 | brushSize = 16; 49 | brushClick = 4; 50 | } else if (brushClick == 4) { 51 | brushSize = 4; 52 | brushClick = 1; 53 | } else { 54 | brushSize = 8; 55 | brushClick = 2; 56 | } 57 | }); 58 | 59 | document.getElementById("reset").addEventListener("click", load); 60 | 61 | function load() { 62 | getcanvas = document.getElementById("drawCanvas"); 63 | 64 | brushClick = 0; 65 | brushSize = 4; 66 | 67 | ctx = getcanvas.getContext("2d"); 68 | ctx.clearRect(0, 0, symmetry, symmetry); 69 | 70 | ctx.strokeStyle = "rgba(0,0,0,0.2)"; 71 | ctx.lineWidth = 0.5; 72 | // Guides lines 73 | 74 | getcanvas.addEventListener("mousemove", draw); 75 | getcanvas.addEventListener("touchmove", draw); 76 | 77 | /* */ 78 | } 79 | 80 | function draw(e) { 81 | var coord = getLocalCoordinates(e); 82 | // console.log(" getLocalCoordinates[0] " + coord[0]); 83 | 84 | var x = coord[0]; 85 | var y = coord[1]; 86 | 87 | // (2 * Math.PI) / 16 88 | 89 | ctx.strokeStyle = color; 90 | ctx.lineWidth = brushSize; 91 | 92 | if (e.buttons == 1) { 93 | drawLine(prevX, prevY, x, y); 94 | } else if (e.type == "touchmove") { 95 | console.log(e.type == "touchmove"); 96 | prevX = x; 97 | prevY = y; 98 | drawLine(prevX, prevY, x, y); 99 | } 100 | prevX = x; 101 | prevY = y; 102 | 103 | // console.log("Symm Y : " + prevY); 104 | // console.log("Symm X : " + prevX); 105 | } 106 | 107 | function getSymmetryPoints(x, y) { 108 | // The coordinate system has its origin at the center of the canvas, 109 | // has up as 0 degrees, right as 90 deg, down as 180 deg, and left as 270 deg. 110 | var ctrX = symmetry / 2; 111 | var ctrY = symmetry / 2; 112 | var relX = x - ctrX; 113 | var relY = ctrY - y; 114 | var dist = Math.hypot(relX, relY); 115 | var angle = Math.atan2(relX, relY); // Radians 116 | var result = []; 117 | for (var i = 0; i < radian; i++) { 118 | var theta = angle + ((Math.PI * 2) / radian) * i; // Radians 119 | x = ctrX + Math.sin(theta) * dist; 120 | y = ctrY - Math.cos(theta) * dist; 121 | result.push([x, y]); 122 | if (true) { 123 | x = ctrX - Math.sin(theta) * dist; 124 | result.push([x, y]); 125 | } 126 | } 127 | 128 | return result; 129 | } 130 | 131 | function drawLine(x1, y1, x2, y2) { 132 | startPoints = getSymmetryPoints(x1, y1); 133 | endPoints = getSymmetryPoints(x2, y2); 134 | 135 | ctx.lineWidth = brushSize; 136 | 137 | ctx.beginPath(); 138 | ctx.lineCap = "round"; 139 | 140 | for (var i = 0; i < startPoints.length; i++) { 141 | ctx.moveTo(startPoints[i][0], startPoints[i][1]); 142 | ctx.lineTo(endPoints[i][0], endPoints[i][1]); 143 | } 144 | // anti aliased ctx.translate(0.5, 0.5); 145 | 146 | ctx.stroke(); 147 | 148 | ctx.stroke(); 149 | } 150 | 151 | // Get local coor in an array 152 | function getLocalCoordinates(ev) { 153 | if (ev.type == "touchmove") { 154 | var touch = ev.touches[0] || ev.changedTouches[0]; 155 | var realTarget = document.elementFromPoint(touch.clientX, touch.clientY); 156 | ev.offsetX = touch.clientX - realTarget.getBoundingClientRect().x; 157 | ev.offsetY = touch.clientY - realTarget.getBoundingClientRect().y; 158 | } 159 | return [ev.offsetX + 0.5, ev.offsetY + 0.5]; 160 | } 161 | -------------------------------------------------------------------------------- /symmetry.js: -------------------------------------------------------------------------------- 1 | var canvas = document.getElementById("drawCanvas"); 2 | 3 | var style = canvas.style; 4 | 5 | var getcanvas, ctx, prevX, prevY; 6 | let symmetry = canvas.width; 7 | let xCenter = symmetry / 2; 8 | let radian = 8; 9 | let brushSize = 4; 10 | let color = "#1A202C"; 11 | let brushClick = 0; 12 | 13 | /** 14 | * Colors button event listner example 15 | * 16 | document.getElementById("red").addEventListener("click", () => { 17 | color = "#FC8181"; 18 | }); 19 | 20 | document.getElementById("blue").addEventListener("click", () => { 21 | color = "#63B3ED"; 22 | }); 23 | document.getElementById("green").addEventListener("click", () => { 24 | color = "#4FD1C5"; 25 | }); 26 | document.getElementById("darkgray").addEventListener("click", () => { 27 | color = "#1A202C"; 28 | }); 29 | document.getElementById("purple").addEventListener("click", () => { 30 | color = "#9F7AEA"; 31 | }); 32 | document.getElementById("eraser").addEventListener("click", () => { 33 | color = "#fff"; 34 | }); 35 | */ 36 | 37 | addEventListener("load", load); 38 | 39 | /** 40 | * 41 | * Brush size event listener example 42 | * 43 | * document.getElementById("brush").addEventListener("click", () => { 44 | console.log(brushClick); 45 | if (brushClick == 1) { 46 | brushSize = 8; 47 | brushClick = 2; 48 | } else if (brushClick == 2) { 49 | brushSize = 12; 50 | brushClick = 3; 51 | } else if (brushClick == 3) { 52 | brushSize = 16; 53 | brushClick = 4; 54 | } else if (brushClick == 4) { 55 | brushSize = 4; 56 | brushClick = 1; 57 | } else { 58 | brushSize = 8; 59 | brushClick = 2; 60 | } 61 | }); 62 | */ 63 | 64 | /** 65 | * To reset the canvas, simply run the load function 66 | * document.getElementById("reset").addEventListener("click", load); 67 | */ 68 | 69 | function load() { 70 | /** 71 | * On your DOM, your Canvas ID must be drawCanvas 72 | */ 73 | getcanvas = document.getElementById("drawCanvas"); 74 | 75 | brushClick = 0; 76 | brushSize = 4; 77 | 78 | ctx = getcanvas.getContext("2d"); 79 | ctx.clearRect(0, 0, symmetry, symmetry); 80 | 81 | ctx.strokeStyle = "rgba(0,0,0,0.2)"; 82 | ctx.lineWidth = 0.5; 83 | // Guides lines 84 | 85 | getcanvas.addEventListener("mousemove", draw); 86 | getcanvas.addEventListener("touchmove", draw); 87 | 88 | /* */ 89 | } 90 | 91 | function draw(e) { 92 | var coord = getLocalCoordinates(e); 93 | // console.log(" getLocalCoordinates[0] " + coord[0]); 94 | 95 | var x = coord[0]; 96 | var y = coord[1]; 97 | 98 | // (2 * Math.PI) / 16 99 | 100 | ctx.strokeStyle = color; 101 | ctx.lineWidth = brushSize; 102 | 103 | if (e.buttons == 1) { 104 | drawLine(prevX, prevY, x, y); 105 | } else if (e.type == "touchmove") { 106 | console.log(e.type == "touchmove"); 107 | prevX = x; 108 | prevY = y; 109 | drawLine(prevX, prevY, x, y); 110 | } 111 | prevX = x; 112 | prevY = y; 113 | } 114 | 115 | function getSymmetryPoints(x, y) { 116 | // The coordinate system has its origin at the center of the canvas, 117 | // has up as 0 degrees, right as 90 deg, down as 180 deg, and left as 270 deg. 118 | var ctrX = symmetry / 2; 119 | var ctrY = symmetry / 2; 120 | var relX = x - ctrX; 121 | var relY = ctrY - y; 122 | var dist = Math.hypot(relX, relY); 123 | var angle = Math.atan2(relX, relY); // Radians 124 | var result = []; 125 | for (var i = 0; i < radian; i++) { 126 | var theta = angle + ((Math.PI * 2) / radian) * i; // Radians 127 | x = ctrX + Math.sin(theta) * dist; 128 | y = ctrY - Math.cos(theta) * dist; 129 | result.push([x, y]); 130 | if (true) { 131 | x = ctrX - Math.sin(theta) * dist; 132 | result.push([x, y]); 133 | } 134 | } 135 | 136 | return result; 137 | } 138 | 139 | function drawLine(x1, y1, x2, y2) { 140 | startPoints = getSymmetryPoints(x1, y1); 141 | endPoints = getSymmetryPoints(x2, y2); 142 | 143 | ctx.lineWidth = brushSize; 144 | 145 | ctx.beginPath(); 146 | ctx.lineCap = "round"; 147 | 148 | for (var i = 0; i < startPoints.length; i++) { 149 | ctx.moveTo(startPoints[i][0], startPoints[i][1]); 150 | ctx.lineTo(endPoints[i][0], endPoints[i][1]); 151 | } 152 | 153 | ctx.stroke(); 154 | 155 | ctx.stroke(); 156 | } 157 | 158 | // Get local coor in an array 159 | function getLocalCoordinates(ev) { 160 | if (ev.type == "touchmove") { 161 | var touch = ev.touches[0] || ev.changedTouches[0]; 162 | var realTarget = document.elementFromPoint(touch.clientX, touch.clientY); 163 | ev.offsetX = touch.clientX - realTarget.getBoundingClientRect().x; 164 | ev.offsetY = touch.clientY - realTarget.getBoundingClientRect().y; 165 | } 166 | return [ev.offsetX + 0.5, ev.offsetY + 0.5]; 167 | } 168 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 14 | 15 | 16 | 17 | 18 | 19 | JustMandala - Let's Chill Out! 20 | 21 | 22 | 23 | 24 |
28 |
31 | 35 | 39 | 43 | 47 | 51 |
52 |
55 | 66 | 67 | 73 | 80 | 84 | 85 | 86 |
87 |
88 | 89 |
90 | 96 |
97 | 98 | 121 | 122 | 123 | --------------------------------------------------------------------------------