SVG / JS Gooey Goodness...
Drag from edges to split.
Drag from center to move.
Goo may be fused back together.
Has some kinks, but for the most part, works as intended...
A Pen by Tiffany Rayside on CodePen.
SVG / JS Gooey Goodness...
Drag from edges to split.
Drag from center to move.
Goo may be fused back together.
Has some kinks, but for the most part, works as intended...
A Pen by Tiffany Rayside on CodePen.
| <!-- | |
| Drag from edges to split. | |
| Drag from center to move. | |
| Goo may be fused back together. | |
| !--> | |
| <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100%" height="100%" viewBox="0,0,800,800" version="1.1" fill = 'hsla(166, 95%, 60%, .8)' > | |
| <text x="300" y="410" font-size='3em' fill="hsla(166, 95%, 75%, 1)">GOOey</text> | |
| </svg> |
| /* | |
| This is pretty much a mess. | |
| Should be cleaned up && | |
| could probably be streamlined a bit, | |
| but it works. | |
| */ | |
| var _svg = "http://www.w3.org/2000/svg"; | |
| var _xlnk = "http://www.w3.org/1999/xlink"; | |
| var midX = 400; | |
| var midY = 400; | |
| var hit = true; | |
| var den = 85; | |
| window.onload = function set() { | |
| new Goo(200, midX, midY); | |
| }; | |
| function Goo(radius, a, b) { | |
| var _goo = this; | |
| this.c1rad = radius; | |
| this.c1p1 = a; | |
| this.c1p2 = b; | |
| this._c1p1_ = a; | |
| this._c1p2_ = b; | |
| this.msdn = [a, b]; | |
| this.fuse = den; | |
| this.c2rad = 50; | |
| this.c2p1 = 0; | |
| this.c2p2 = 0 - this.c1rad + this.c2rad - 1; | |
| this.pth = document.createElementNS(_svg, "path"); | |
| this.pth.setAttributeNS(null, "class", "pth"); | |
| this.pth.objRef = this; | |
| this.reset = function() { | |
| this.pth.setAttributeNS(null, "transform", "translate(" + this.c1p1 + "," + this.c1p2 + ")"); | |
| var pthd = "m 0 " + -this.c1rad + " A " + this.c1rad + " " + this.c1rad + " 0 1 1 0 " + this.c1rad; | |
| pthd += "A " + this.c1rad + " " + this.c1rad + " 0 1 1 0 " + -this.c1rad; | |
| this.pth.setAttribute("d", pthd); | |
| }; | |
| this.split = function(dist, ang) { | |
| this.pth.setAttributeNS(null, "transform", "translate(" + this.c1p1 + "," + this.c1p2 + ") rotate(" + ang + ",0,0)"); | |
| this.c2p2 = 0 - this.c1mrad + this.c2rad - dist; | |
| this.fuse = den; | |
| var fina = 0 - this.c1mnrad - this.fuse * 2 - this.c2rad; | |
| var srta = 0 - this.c1mrad + this.c2rad - 1; | |
| var diffa = srta - fina; | |
| var _diffa = this.c2p2 - fina; | |
| var dper = _diffa / diffa; | |
| this.c1rad = this.c1mnrad + (this.c1mrad - this.c1mnrad) * dper; | |
| var angA = this.c1rad + this.fuse; | |
| var angB = this.c2rad + this.fuse; | |
| var angC = Math.abs(this.c2p2 - 0); | |
| var aper = (angA + angB + angC) / 2; | |
| var aa = Math.sqrt(Math.abs(aper * (aper - angA) * (aper - angB) * (aper - angC))); | |
| if (angC >= angA) { | |
| var anght = 2 * aa / angC; | |
| var angbi = Math.sqrt(Math.pow(angA, 2) - Math.pow(anght, 2)); | |
| } else { | |
| var anght = 2 * aa / angA; | |
| var angbi = Math.sqrt(Math.pow(angC, 2) - Math.pow(anght, 2)); | |
| } | |
| var c1tan = anght / angbi; | |
| var c1ang = Math.atan(c1tan); | |
| var c1sin = Math.sin(c1ang); | |
| var c1iX = c1sin * this.c1rad; | |
| var c1cos = Math.cos(c1ang); | |
| var c1iY = c1cos * this.c1rad; | |
| var fusec1ht = 0 + c1sin * (this.c1rad + this.fuse); | |
| var fusec1bi = 0 - c1cos * (this.c1rad + this.fuse); | |
| var pt1X = 0 - c1iX; | |
| var pt1Y = 0 - c1iY; | |
| var pt2X = 0 + c1iX; | |
| var pt2Y = 0 - c1iY; | |
| var c2tan = (this.c2p2 - fusec1bi) / (this.c2p1 - fusec1ht); | |
| var c2ang = Math.atan(c2tan); | |
| var c2iX = fusec1ht - Math.cos(c2ang) * (this.fuse); | |
| var c2iY = fusec1bi - Math.sin(c2ang) * (this.fuse); | |
| var pthd = "M " + pt1X + " " + pt1Y + " A " + this.c1rad + " " + this.c1rad + " 0 1 0 " + pt2X + " " + pt2Y; | |
| if (fusec1ht - this.fuse <= 0 && this.c2p2 < fusec1bi) { | |
| var xxY = cY_X(fusec1ht, fusec1bi, this.fuse, 0); | |
| pthd += "A " + this.fuse + " " + this.fuse + " 0 0 1 0 " + (fusec1bi + xxY); | |
| pthd += "m 0 -" + (xxY * 2); | |
| } | |
| pthd += "A " + this.fuse + " " + this.fuse + " 0 0 1 " + c2iX + " " + c2iY; | |
| var lcfl = 1; | |
| if (fusec1bi < this.c2p2) { | |
| lcfl = 0; | |
| } | |
| pthd += "a " + this.c2rad + " " + this.c2rad + " 0 " + lcfl + " 0 " + ((c2iX - 0) * -2) + " 0"; | |
| if (fusec1ht - this.fuse <= 0 && this.c2p2 < fusec1bi) { | |
| pthd += "A " + this.fuse + " " + this.fuse + " 0 0 1 0 " + (fusec1bi - xxY); | |
| pthd += "m 0 " + (xxY * 2); | |
| } | |
| pthd += "A " + this.fuse + " " + this.fuse + " 0 0 1 " + pt1X + " " + pt1Y; | |
| pthd += "A " + this.fuse + " " + this.fuse + " 0 0 1 " + pt1X + " " + pt1Y; | |
| this.pth.setAttribute("d", pthd); | |
| }; | |
| this.dfuse = function(dist, ang) { | |
| this.pth.setAttributeNS(null, "class", "pth joining"); | |
| this.pth.setAttributeNS(null, "transform", "translate(" + this.c1p1 + "," + this.c1p2 + ") rotate(" + ang + ",0,0)"); | |
| this.c2p2 = 0 - this.c1mrad + this.c2rad - dist; | |
| this.fCmnrad = 1; | |
| this.fCmrad = 200; | |
| var srta = 0 - this.c1mnrad - this.c2rad; | |
| var fina = 0 - this.c1mrad + this.c2rad - 1; | |
| var diffa = srta - fina; | |
| var _diffa = this.c2p2 - fina; | |
| var dper = _diffa / diffa; | |
| this.fuse = this.fCmrad - (this.fCmrad - this.fCmnrad) * dper; | |
| this.c1rad = this.c1mrad - (this.c1mrad - this.c1mnrad) * dper; | |
| var angA = this.c1rad + this.fuse; | |
| var angB = this.c2rad + this.fuse; | |
| var angC = Math.abs(this.c2p2); | |
| var aper = (angA + angB + angC) / 2; | |
| var aa = Math.sqrt(aper * (aper - angA) * (aper - angB) * (aper - angC)); | |
| if (angC >= angA) { | |
| var anght = 2 * aa / angC; | |
| var angbi = Math.sqrt(Math.pow(angA, 2) - Math.pow(anght, 2)); | |
| } else { | |
| var anght = 2 * aa / angA; | |
| var angbi = Math.sqrt(Math.pow(angC, 2) - Math.pow(anght, 2)); | |
| } | |
| var c1tan = anght / angbi; | |
| var c1ang = Math.atan(c1tan); | |
| var c1sin = Math.sin(c1ang); | |
| var c1iX = c1sin * this.c1rad; | |
| var c1cos = Math.cos(c1ang); | |
| var c1iY = c1cos * this.c1rad; | |
| var fusec1ht = c1sin * (this.c1rad + this.fuse); | |
| var fusec1bi = -c1cos * (this.c1rad + this.fuse); | |
| var pt1X = -c1iX; | |
| var pt1Y = -c1iY; | |
| var pt2X = c1iX; | |
| var pt2Y = -c1iY; | |
| var c2tan = (this.c2p2 - fusec1bi) / (this.c2p1 - fusec1ht); | |
| var c2ang = Math.atan(c2tan); | |
| var c2iX = fusec1ht - Math.cos(c2ang) * (this.fuse); | |
| var c2iY = fusec1bi - Math.sin(c2ang) * (this.fuse); | |
| var pthd = "M " + pt1X + " " + pt1Y + " A " + this.c1rad + " " + this.c1rad + " 0 1 0 " + pt2X + " " + pt2Y; | |
| if (fusec1ht - this.fuse <= 0 && this.c2p2 < fusec1bi) { | |
| var xxY = cY_X(fusec1ht, fusec1bi, this.fuse, 0); | |
| pthd += "A " + this.fuse + " " + this.fuse + " 0 0 1 0 " + (fusec1bi + xxY); | |
| pthd += "m 0 -" + (xxY * 2); | |
| } | |
| pthd += "A " + this.fuse + " " + this.fuse + " 0 0 1 " + c2iX + " " + c2iY; | |
| var lcfl = 1; | |
| if (fusec1bi < this.c2p2) { | |
| lcfl = 0; | |
| } | |
| pthd += "a " + this.c2rad + " " + this.c2rad + " 0 " + lcfl + " 0 " + (c2iX * -2) + " 0"; | |
| if (fusec1ht - this.fuse <= 0 && this.c2p2 < fusec1bi) { | |
| pthd += "A " + this.fuse + " " + this.fuse + " 0 0 1 0 " + (fusec1bi - xxY); | |
| pthd += "m 0 " + (xxY * 2); | |
| } | |
| pthd += "A " + this.fuse + " " + this.fuse + " 0 0 1 " + pt1X + " " + pt1Y; | |
| pthd += "A " + this.fuse + " " + this.fuse + " 0 0 1 " + pt1X + " " + pt1Y; | |
| this.pth.setAttribute("d", pthd); | |
| }; | |
| this.brk = function(pts) { | |
| var inc = den / 4; | |
| var curra = this.c2p2 + inc; | |
| if (curra > -this.c1rad + this.c2rad - 1) { | |
| this.c1rad = this.c1mrad; | |
| this.reset(); | |
| } else { | |
| var dist = -curra - (this.c1mrad - this.c2rad); | |
| var ang = calc([this.c1p1, this.c1p2], pts); | |
| this.split(dist, ang); | |
| setTimeout(function() { | |
| _goo.brk(pts); | |
| }, 25); | |
| } | |
| } | |
| this.fuse_ = function(pts) { | |
| var inc = 20; | |
| var curra = this.c2p2 + inc; | |
| if (curra > -this.c1rad + this.c2rad - 1) { | |
| this.c1rad = this.c1mrad; | |
| this.pth.setAttributeNS(null, "class", "pth"); | |
| this.reset(); | |
| } else { | |
| var dist = -curra - (this.c1mrad - this.c2rad); | |
| var ang = calc([this.c1p1, this.c1p2], pts); | |
| this.dfuse(dist, ang); | |
| setTimeout(function() { | |
| _goo.fuse_(pts); | |
| }, 25); | |
| } | |
| } | |
| this.mousedown = function(e) { | |
| _goo.msdn = _toSVG(e.clientX, e.clientY); | |
| _goo._c1p1_ = _goo.c1p1; | |
| _goo._c1p2_ = _goo.c1p2; | |
| _goo.od = Math.sqrt(Math.pow(_goo.msdn[0] - _goo.c1p1, 2) + Math.pow(_goo.msdn[1] - _goo.c1p2, 2)); | |
| _goo.c2rad = _goo.c1rad - _goo.od; | |
| if (_goo.od < 20) { | |
| document.addEventListener("mousemove", _goo.mousemove, false); | |
| document.addEventListener("mouseup", _goo.mouseup, false); | |
| } else { | |
| var c1area = Math.PI * Math.pow(_goo.c1rad, 2); | |
| var c2area = Math.PI * Math.pow(_goo.c2rad, 2); | |
| var nxtarea = c1area - c2area; | |
| _goo.c1mrad = _goo.c1rad; | |
| _goo.c1mnrad = Math.sqrt(nxtarea / Math.PI); | |
| document.addEventListener("mousemove", _goo.mvsplit, false); | |
| document.addEventListener("mouseup", _goo.msupSplit, false); | |
| } | |
| e.stopPropagation(); | |
| e.preventDefault(); | |
| }; | |
| this.mousemove = function(e) { | |
| var pts = _toSVG(e.clientX, e.clientY); | |
| _goo.pth.setAttributeNS(null, "class", "pth"); | |
| _goo.c1p1 = _goo._c1p1_ + pts[0] - _goo.msdn[0]; | |
| _goo.c1p2 = _goo._c1p2_ + pts[1] - _goo.msdn[1]; | |
| if (hit == true) { | |
| var paths = document.getElementsByTagName("path"); | |
| for (var i = 0; i < paths.length; i++) { | |
| var objRef = paths[i].objRef; | |
| var dist = Math.sqrt(Math.pow(_goo.c1p1 - objRef.c1p1, 2) + Math.pow(_goo.c1p2 - objRef.c1p2, 2)) | |
| if (paths[i] != _goo.pth && dist < _goo.c1rad + objRef.c1rad) { | |
| var c1area = Math.PI * Math.pow(objRef.c1rad, 2); | |
| var c2area = Math.PI * Math.pow(_goo.c1rad, 2); | |
| var nxtarea = c1area + c2area; | |
| if (_goo.c1rad < objRef.c1rad) { | |
| objRef.c1mnrad = objRef.c1rad; | |
| objRef.c1mrad = Math.sqrt(nxtarea / Math.PI); | |
| objRef.c2rad = _goo.c1rad; | |
| objRef.c2oa = _goo._c1p1_; | |
| objRef.c2ob = _goo._c1p2_; | |
| objRef.msdn = _goo.msdn; | |
| var dd = dist - objRef.c1mrad + objRef.c2rad; | |
| if (dd < 1) { | |
| dd = 1; | |
| } | |
| objRef.dfuse(dd, calc([objRef.c1p1, objRef.c1p2], [_goo.c1p1, _goo.c1p2])); | |
| document.addEventListener("mousemove", objRef.mvfuse, false); | |
| document.addEventListener("mouseup", objRef.msupFuse, false); | |
| document.removeEventListener("mousemove", _goo.mousemove, false); | |
| document.removeEventListener("mouseup", _goo.mouseup, false); | |
| _goo.pth.parentNode.removeChild(_goo.pth); | |
| } else { | |
| objRef.c1mnrad = _goo.c1rad; | |
| objRef.c1mrad = Math.sqrt(nxtarea / Math.PI); | |
| objRef.c2rad = objRef.c1rad; | |
| objRef.c2oa = objRef.c1p1; | |
| objRef.c2ob = objRef.c1p2; | |
| objRef.c1rad = _goo.c1rad; | |
| objRef.c1p1 = _goo.c1p1; | |
| objRef.c1p2 = _goo.c1p2; | |
| objRef._c1p1_ = _goo._c1p1_; | |
| objRef._c1p2_ = _goo._c1p2_; | |
| objRef.msdn = _goo.msdn; | |
| var dd = dist - objRef.c1mrad + objRef.c2rad; | |
| if (dd < 1) { | |
| dd = 1; | |
| } | |
| objRef.dfuse(dd, calc([objRef.c1p1, objRef.c1p2], [objRef.c2oa, objRef.c2ob])); | |
| document.addEventListener("mousemove", objRef.mvfusea, false); | |
| document.addEventListener("mouseup", objRef.msupfusea, false); | |
| document.removeEventListener("mousemove", _goo.mousemove, false); | |
| document.removeEventListener("mouseup", _goo.mouseup, false); | |
| _goo.pth.parentNode.removeChild(_goo.pth); | |
| } | |
| break; | |
| } | |
| } | |
| } | |
| _goo.reset(); | |
| e.stopPropagation(); | |
| e.preventDefault(); | |
| }; | |
| this.mvsplit = function(e) { | |
| var pts = _toSVG(e.clientX, e.clientY); | |
| var dist = Math.sqrt(Math.pow(pts[0] - _goo.c1p1, 2) + Math.pow(pts[1] - _goo.c1p2, 2)); | |
| if (dist > _goo.c1rad + _goo.fuse * 2 + _goo.c2rad) { | |
| var apart = new Goo(_goo.c2rad, pts[0], pts[1]); | |
| apart.pth.setAttributeNS(null, "class", "pth joining"); | |
| document.addEventListener("mousemove", apart.mousemove, false); | |
| document.addEventListener("mouseup", apart.mouseup, false); | |
| document.removeEventListener("mousemove", _goo.mvsplit, false); | |
| document.removeEventListener("mouseup", _goo.msupSplit, false); | |
| this.c1rad = this.c1mnrad; | |
| _goo.reset(); | |
| } else { | |
| var dd = dist - _goo.od; | |
| if (dd < 1) { | |
| dd = 1; | |
| } | |
| _goo.split(dd, calc([_goo.c1p1, _goo.c1p2], pts)); | |
| } | |
| e.stopPropagation(); | |
| e.preventDefault(); | |
| }; | |
| this.mvfuse = function(e) { | |
| var pts = _toSVG(e.clientX, e.clientY); | |
| var dist = Math.sqrt(Math.pow(_goo.c2oa + pts[0] - _goo.msdn[0] - _goo.c1p1, 2) + Math.pow(_goo.c2ob + pts[1] - _goo.msdn[1] - _goo.c1p2, 2)); | |
| if (dist > _goo.c1mnrad + _goo.c2rad) { | |
| var apart = new Goo(_goo.c2rad, pts[0], pts[1]); | |
| document.addEventListener("mousemove", apart.mousemove, false); | |
| document.addEventListener("mouseup", apart.mouseup, false); | |
| document.removeEventListener("mousemove", _goo.mvfuse, false); | |
| document.removeEventListener("mouseup", _goo.msupFuse, false); | |
| _goo.pth.setAttributeNS(null, "class", "pth"); | |
| _goo.c1rad = _goo.c1mnrad; | |
| _goo.reset(); | |
| } else { | |
| var dd = dist - _goo.c1mrad + _goo.c2rad; | |
| if (dd < 1) { | |
| dd = 1; | |
| } | |
| _goo.dfuse(dd, calc([_goo.c1p1, _goo.c1p2], [_goo.c2oa + pts[0] - _goo.msdn[0], _goo.c2ob + pts[1] - _goo.msdn[1]])); | |
| } | |
| e.stopPropagation(); | |
| e.preventDefault(); | |
| }; | |
| this.mvfusea = function(e) { | |
| var pts = _toSVG(e.clientX, e.clientY); | |
| _goo.c1p1 = _goo._c1p1_ + pts[0] - _goo.msdn[0]; | |
| _goo.c1p2 = _goo._c1p2_ + pts[1] - _goo.msdn[1]; | |
| var dist = Math.sqrt(Math.pow(_goo.c1p1 - _goo.c2oa, 2) + Math.pow(_goo.c1p2 - _goo.c2ob, 2)); | |
| if (dist > _goo.c1mnrad + _goo.c2rad) { | |
| var apart = new Goo(_goo.c2rad, _goo.c2oa, _goo.c2ob); | |
| document.addEventListener("mousemove", _goo.mousemove, false); | |
| document.addEventListener("mouseup", _goo.mouseup, false); | |
| document.removeEventListener("mousemove", _goo.mvfusea, false); | |
| document.removeEventListener("mouseup", _goo.msupfusea, false); | |
| _goo.c1rad = _goo.c1mnrad; | |
| _goo.reset(); | |
| } else { | |
| var dd = dist - _goo.c1mrad + _goo.c2rad; | |
| if (dd < 1) { | |
| dd = 1; | |
| } | |
| _goo.dfuse(dd, calc([_goo.c1p1, _goo.c1p2], [_goo.c2oa, _goo.c2ob])); | |
| } | |
| e.stopPropagation(); | |
| e.preventDefault(); | |
| }; | |
| this.mouseup = function(e) { | |
| _goo.pth.setAttributeNS(null, "class", "pth"); | |
| document.removeEventListener("mousemove", _goo.mousemove, false); | |
| document.removeEventListener("mouseup", _goo.mouseup, false); | |
| e.stopPropagation(); | |
| e.preventDefault(); | |
| }; | |
| this.msupSplit = function(e) { | |
| var pts = _toSVG(e.clientX, e.clientY); | |
| _goo.brk(pts); | |
| document.removeEventListener("mousemove", _goo.mvsplit, false); | |
| document.removeEventListener("mouseup", _goo.msupSplit, false); | |
| e.stopPropagation(); | |
| e.preventDefault(); | |
| }; | |
| this.msupFuse = function(e) { | |
| var pts = _toSVG(e.clientX, e.clientY); | |
| _goo.fuse_(pts); | |
| document.removeEventListener("mousemove", _goo.mvfuse, false); | |
| document.removeEventListener("mouseup", _goo.msupFuse, false); | |
| e.stopPropagation(); | |
| e.preventDefault(); | |
| }; | |
| this.msupfusea = function(e) { | |
| _goo.fuse_([_goo.c2oa, _goo.c2ob]); | |
| document.removeEventListener("mousemove", _goo.mvfusea, false); | |
| document.removeEventListener("mouseup", _goo.msupfusea, false); | |
| e.stopPropagation(); | |
| e.preventDefault(); | |
| }; | |
| this.pth.addEventListener("mousedown", this.mousedown, false); | |
| document.getElementsByTagName("svg")[0].appendChild(this.pth); | |
| this.reset(); | |
| }; | |
| function density(xPos, xPer) { | |
| den = 10 + Math.round(xPer * 400); | |
| }; | |
| function vws() { | |
| var size = [0, 0]; | |
| size = [window.innerWidth, window.innerHeight]; | |
| return size; | |
| }; | |
| function _toSVG(gx, gy) { | |
| var svgpts = [0, 0]; | |
| var svg = document.getElementsByTagName("svg")[0]; | |
| var vb = svg.viewBox.baseVal; | |
| var vbw = vb.width; | |
| var vbh = vb.height; | |
| var vbr = vbw / vbh; | |
| var vps = vws(); | |
| var vpr = vps[0] / vps[1]; | |
| if (vbr <= vpr) { | |
| svgpts[1] = gy * (vbh / vps[1]); | |
| var vbgw = vbw * (vps[1] / vbh); | |
| var vboX = (vps[0] - vbgw) / 2; | |
| svgpts[0] = (gx - vboX) * (vbh / vps[1]); | |
| } else { | |
| svgpts[0] = gx * (vbw / vps[0]); | |
| var vbgh = vbh * (vps[0] / vbw); | |
| var vboY = (vps[1] - vbgh) / 2; | |
| svgpts[1] = (gy - vboY) * (vbw / vps[0]); | |
| } | |
| return svgpts; | |
| }; | |
| function cY_X(a, b, r, x) { | |
| return Math.sqrt(Math.pow(r, 2) - Math.pow(x - a, 2)); | |
| }; | |
| function calc(o, pt) { | |
| var tan = (pt[1] - o[1]) / (pt[0] - o[0]); | |
| var ang = Math.atan(tan) / Math.PI * 180 + 90; | |
| if (pt[0] < o[0]) { | |
| ang += 180; | |
| } | |
| return ang; | |
| } | |
| console.log('Always with ❤, @tmrDevelops'); |
| @import url(http://fonts.googleapis.com/css?family=Chango); | |
| html { | |
| height: 100%; | |
| margin: 0; | |
| padding: 0; | |
| } | |
| body { | |
| cursor: move; | |
| height: 100%; | |
| overflow: hidden; | |
| background: hsla(166, 95%, 25%, 1); | |
| background-position: 50% 50%; | |
| font-family: 'Chango', cursive; | |
| } | |
| svg text::selection { | |
| background: none; | |
| } |