Javascript: how to create a button element without text content - javascript

In html, I have a button element with no text. It has a child svg element with some paths and rectangles. It works fine:
I try to create this in javascript. The problem is, that the button is not visible. If I set some text to it with textContent or innerHtml, the button is visible with the text, but the svg is not there. How can I create this button in javascript? This is the code:
var myButton = document.createElement("button");
myButton.setAttribute("class", "my-button");
myButton.setAttribute("id", "foo");
var mySVG = document.createElement("svg");
mySVG.setAttribute("id", "my-svg");
mySVG.setAttribute("viewBox", "0 0 12.25 15.45");
var icon1 = document.createElement("g");
icon1.setAttribute("class", "g-element1");
icon1.setAttribute("id", "g1");
var iconPath = document.createElement("path");
iconPath.setAttribute("d", "M0,25L0,0l12.25,7.7L0,15.45z");
var icon2 = document.createElement("g");
icon2.setAttribute("class", "g-element2");
icon2.setAttribute("id", "g2");
var rect1 = document.createElement("rect");
rect1.setAttribute("x", "0");
rect1.setAttribute("y", "0");
rect1.setAttribute("width", "4.1");
rect1.setAttribute("height", "15.45");
var rect2 = document.createElement("rect");
rect2.setAttribute("x", "8.1");
rect2.setAttribute("y", "0");
rect2.setAttribute("width", "4.1");
rect2.setAttribute("height", "15.45");
icon1.appendChild(iconPath);
icon2.appendChild(rect1);
icon2.appendChild(rect2);
mySVG.appendChild(icon1);
mySVG.appendChild(icon2);
myButton.appendChild(mySVG);
document.getElementById('some-element').appendChild(myButton)
.my-button {
font-size: 14px;
height: 17px;
cursor: pointer;
margin-left: 5px;
&:hover, &:focus {
opacity: .8;
}
}
<div id="some-element">
<button class="my-button" id="foo">
<svg id="my-svg" viewBox="0 0 12.25 15.45">
<g class="g-element1" id="g1">
<path d="M0,25L0,0l12.25,7.7L0,15.45z"/>
</g>
<g class="g-element2" id="g2">
<rect x="0" y="0" width="4.1" height="15.45"/>
<rect x="8.1" y="0" width="4.1" height="15.45"/>
</g>
</svg>
</button>
</div>
Also when I create just the button in javascript and I set no text to it (and no svg either), the button is not visible.

When creating SVG elements (including elements inside the the SVG tage) using JavaScript, you need to use document.createElementNS(namespaceURI, qualifiedName) with the appropriate namespace URI http://www.w3.org/2000/svg. You also need to assign a height to the SVG element.
Because you have to use the namespace for every element that you're creating within the SVG tag as well as the SVG tag itself, you may want to curry the function to save space and prevent typos:
const createSVGElement = qn => document.createElementNS("http://www.w3.org/2000/svg", qn);
Here's your code fixed:
var myButton = document.createElement("button");
myButton.setAttribute("class", "my-button");
myButton.setAttribute("id", "foo");
const createSVGElement = qn => document.createElementNS("http://www.w3.org/2000/svg", qn);
var mySVG = createSVGElement("svg");
mySVG.setAttribute("id", "my-svg");
mySVG.setAttribute('height', "14px");
mySVG.setAttribute("viewBox", "0 0 12.25 15.45");
var icon1 = createSVGElement("g");
icon1.setAttribute("class", "g-element1");
icon1.setAttribute("id", "g1");
var iconPath = createSVGElement("path");
iconPath.setAttribute("d", "M0,25L0,0l12.25,7.7L0,15.45z");
var icon2 = createSVGElement("g");
icon2.setAttribute("class", "g-element2");
icon2.setAttribute("id", "g2");
var rect1 = createSVGElement("rect");
rect1.setAttribute("x", "0");
rect1.setAttribute("y", "0");
rect1.setAttribute("width", "4.1");
rect1.setAttribute("height", "15.45");
var rect2 = createSVGElement("rect");
rect2.setAttribute("x", "8.1");
rect2.setAttribute("y", "0");
rect2.setAttribute("width", "4.1");
rect2.setAttribute("height", "15.45");
icon1.appendChild(iconPath);
icon2.appendChild(rect1);
icon2.appendChild(rect2);
mySVG.appendChild(icon1);
mySVG.appendChild(icon2);
myButton.appendChild(mySVG);
document.getElementById("some-element").appendChild(myButton);
.my-button {
font-size: 14px;
height: 17px;
cursor: pointer;
margin-left: 5px;
&:hover, &:focus {
opacity: .8;
}
}
<div id="some-element">
<button class="my-button" id="foo">
<svg id="my-svg" viewBox="0 0 12.25 15.45" height="14px">
<g class="g-element1" id="g1">
<path d="M0,25L0,0l12.25,7.7L0,15.45z"/>
</g>
<g class="g-element2" id="g2">
<rect x="0" y="0" width="4.1" height="15.45"/>
<rect x="8.1" y="0" width="4.1" height="15.45"/>
</g>
</svg>
</button>
</div>

The SVG appears to be collapsing to zero width and height inside the button. You can prevent this by setting an explicit width and height on it:
.my-button {
font-size: 14px;
height: 17px;
cursor: pointer;
margin-left: 5px;
&:hover, &:focus {
opacity: .8;
}
}
#my-svg {width: 100%; height: 100%}
<div id="some-element">
<button class="my-button" id="foo">
<svg id="my-svg" viewBox="0 0 12.25 15.45">
<g class="g-element1" id="g1">
<path d="M0,25L0,0l12.25,7.7L0,15.45z"/>
</g>
<g class="g-element2" id="g2">
<rect x="0" y="0" width="4.1" height="15.45"/>
<rect x="8.1" y="0" width="4.1" height="15.45"/>
</g>
</svg>
</button>
</div>
The same should apply whether the SVG is defined inline or script-generated. But note that when generating non-HTML nodes it's necessary to use .createElementNS() and include the namespace, as below:
var myButton = document.createElement("button");
myButton.setAttribute("class", "my-button");
myButton.setAttribute("id", "foo");
var svgns = "http://www.w3.org/2000/svg";
var mySVG = document.createElementNS(svgns, "svg");
mySVG.setAttribute("id", "my-svg");
mySVG.setAttribute("viewBox", "0 0 12.25 15.45");
var icon1 = document.createElementNS(svgns, "g");
icon1.setAttribute("class", "g-element1");
icon1.setAttribute("id", "g1");
var iconPath = document.createElementNS(svgns, "path");
iconPath.setAttribute("d", "M0,25L0,0l12.25,7.7L0,15.45z");
var icon2 = document.createElementNS(svgns, "g");
icon2.setAttribute("class", "g-element2");
icon2.setAttribute("id", "g2");
var rect1 = document.createElementNS(svgns, "rect");
rect1.setAttribute("x", "0");
rect1.setAttribute("y", "0");
rect1.setAttribute("width", "4.1");
rect1.setAttribute("height", "15.45");
var rect2 = document.createElementNS(svgns, "rect");
rect2.setAttribute("x", "8.1");
rect2.setAttribute("y", "0");
rect2.setAttribute("width", "4.1");
rect2.setAttribute("height", "15.45");
icon1.appendChild(iconPath);
icon2.appendChild(rect1);
icon2.appendChild(rect2);
mySVG.appendChild(icon1);
mySVG.appendChild(icon2);
document.getElementById('some-element').appendChild(mySVG)
#my-svg {
width: 100%;
height: 100%
}
button {height: 14px}
<button id="some-element"></div>

Setting the svg element as innerHTML to the button might give you the result.
document.getElementById('foo').innerHTML = '<svg id="my-svg" viewBox="0 0 12.25 15.45"><g class="g-element1" id="g1"><path d="M0,25L0,0l12.25,7.7L0,15.45z"/></g><g class="g-element2" id="g2"><rect x="0" y="0" width="4.1" height="15.45"/><rect x="8.1" y="0" width="4.1" height="15.45"/></g></svg>';
.my-button {
font-size: 14px;
height: 17px;
cursor: pointer;
margin-left: 5px;
&:hover, &:focus {
opacity: .8;
}
}
svg
{
height:100%;
width:100%;
}
<div id="some-element">
<button class="my-button" id="foo">
</button>
</div>

Your code is actually Ok but you will need a way to add the button to the DOM for example if you are to append it to the body, You may have to do something like
document.body.appendChild(playButton)
You can as well add it to an existing element on the webpage

Related

zoom in SVG with place clicked remaining in the same place

For quite a while I have been dealing with this scenario where I want to zoom in on an SVG image by clicking on it (or tapping in case of a touch-screen device), having it so that where you have clicked stays in the same position on the page while zooming in.
What happens at the moment is that when zooming is done, the pivot is not the clicked point but another point. It seems I cannot get to think of the correct formula to translate the svg as zooming in is done. Preferably I would see a solution that doesn't use an external library as the code below is a simplified version of a bigger project that has quite complicated stuff that would propably not be as easy to refactor to another component.
let zoomedIn = false;
let svgViewboxWidth, svgViewboxHeight;
let factor = 1;
function init() {
svgViewboxWidth = 335.2262416700105;
svgViewboxHeight = 304.65863467997406;
let svgContainer = document.getElementById('svgContainer');
svgContainer.addEventListener('click', svgClick);
}
function svgClick(event) {
let newCalcX = 28, newCalcY = -86;
let gZoom = document.getElementById('zoomscale');
if (zoomedIn) {
// zoom out
factor = 1;
newCalcX = 0;
newCalcY = 0;
zoomedIn = false;
} else {
// zoom in
factor = 3;
newCalcX = (event.clientX - 86) * -1;
newCalcY = (event.clientY + 28) * -1;
zoomedIn = true;
}
let transformValue = `translate(${newCalcX}, ${newCalcY}) scale(${factor})`;
gZoom.setAttribute('transform', transformValue);
}
body {
margin: 0px;
padding: 0px;
}
.container {
width: 100%;
height: 100%;
}
<?xml version='1.0' encoding='UTF-8'?>
<html>
<head>
<title>SVG zoom issue</title>
</head>
<body onload="init()">
<div id="svgContainer" class="container">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="view" height="100%" width="100%" viewBox="0 0 335.2262416700105 304.65863467997406" preserveAspectRatio="xMidYMid meet" version="1.1" class="view">
<style>svg { background-color:rgba(64, 64, 64, 0.1); }</style>
<g id="zoomscale">
<line x1="20" x2="310" y1="150" y2="150" style="stroke: #000000; stroke-width: 1px" />
<line x1="165" x2="165" y1="20" y2="280" style="stroke: #000000; stroke-width: 1px" />
<circle cx="165" cy="150" r="20" style="stroke: #000000; stroke-width: 1px; fill: none" />
<circle cx="165" cy="150" r="40" style="stroke: #000000; stroke-width: 1px; fill: none" />
<circle cx="165" cy="150" r="60" style="stroke: #000000; stroke-width: 1px; fill: none" />
<circle cx="165" cy="150" r="90" style="stroke: #000000; stroke-width: 1px; fill: none" />
<circle cx="165" cy="150" r="120" style="stroke: #000000; stroke-width: 1px; fill: none" />
</g>
</svg>
</div>
</body>
<html>
I feel like I am missing something obvious, or maybe I am just not able to calculate the correct translate- values for zooming. Massive thanks to the one(s) who can help me with this!

Divide semi circle with stroke-dasharray and on active color fill

I'm working on an SVG gauge meter here I want to divide the semi-circle into parts when the range slider move on active stroke fill with gradient color. And want to add one more black color running track when the meter needle moves. I had tried using stroke-dasharray but after adding this all colors are coming at a time meter is not working properly.
I want to divide the semi-circle into parts when the range slider move on active stroke fill with gradient color
And I want to add one more black color running track when the meter needle moves.
My code here
/* set radius for all circles */
var r = 400;
var circles = document.querySelectorAll('.circle');
var total_circles = circles.length;
for (var i = 0; i < total_circles; i++) {
circles[i].setAttribute('r', r);
}
/* set meter's wrapper dimension */
var meter_dimension = (r * 2) + 100;
var wrapper = document.querySelector('#wrapper');
wrapper.style.width = meter_dimension + 'px';
wrapper.style.height = meter_dimension + 'px';
/* add strokes to circles */
var cf = 2 * Math.PI * r;
var semi_cf = cf / 2;
var semi_cf_1by3 = semi_cf / 3;
var semi_cf_2by3 = semi_cf_1by3 * 2;
document.querySelector('#outline_curves').setAttribute('stroke-dasharray', semi_cf + ',' + cf);
document.querySelector('#high').setAttribute('stroke-dasharray', semi_cf + ',' + cf);
document.querySelector('#avg').setAttribute('stroke-dasharray', semi_cf_2by3 + ',' + cf);
document.querySelector('#low').setAttribute('stroke-dasharray', semi_cf_1by3 + ',' + cf);
document.querySelector('#outline_ends').setAttribute('stroke-dasharray', 2 + ',' + (semi_cf - 2));
document.querySelector('#mask').setAttribute('stroke-dasharray', semi_cf + ',' + cf);
/*bind range slider event*/
var slider = document.querySelector('#slider');
var lbl = document.querySelector("#lbl");
var mask = document.querySelector('#mask');
var meter_needle = document.querySelector('#meter_needle');
function range_change_event() {
var percent = slider.value
var meter_value = semi_cf - ((percent * semi_cf) / 100);
mask.setAttribute('stroke-dasharray', meter_value + ',' + cf);
meter_needle.style.transform = 'rotate(' + (450 - ((percent * 180) / 100)) + 'deg)';
lbl.textContent = percent + '%';
}
slider.addEventListener('input', range_change_event);
#wrapper {
position: relative;
margin: auto;
}
#meter {
width: 100%;
height: 100%;
transform: rotate(180deg);
}
.circle {
fill: none;
}
.outline,
#mask {
stroke: #F1F1F1;
stroke-width: 65;
}
.range {
stroke-width: 60;
}
#slider,
#lbl {
position: absolute;
}
#slider {
position: absolute;
transform: rotate(180deg);
cursor: pointer;
left: 0;
margin: auto;
right: 0;
top: 58%;
width: 94%;
}
#lbl {
background-color: #4B4C51;
border-radius: 2px;
color: white;
font-family: 'courier new';
font-size: 15pt;
font-weight: bold;
padding: 4px 4px 2px 4px;
right: -48px;
top: 57%;
}
#meter_needle {
height: 40%;
left: 0;
margin: auto;
position: absolute;
right: 0;
top: 10%;
transform-origin: bottom center;
/*orientation fix*/
transform: rotate(450deg);
width: 5px;
background-color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<body>
<div id="wrapper">
<svg id="meter">
<circle id="outline_curves" class="circle outline" cx="50%" cy="50%">
</circle>
<circle id="high" class="circle range" cx="50%" cy="50%" stroke="#FDE47F">
</circle>
<circle id="avg" class="circle range" cx="50%" cy="50%" stroke="#7CCCE5">
</circle>
<circle id="low" class="circle range" cx="50%" cy="50%" stroke="#E04644">
</circle>
<circle id="mask" class="circle" cx="50%" cy="50%" >
</circle>
<circle id="outline_ends" class="circle outline" cx="50%" cy="50%">
</circle>
</svg>
<svg version="1.1" id="meter_needle" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="16.721px" height="93.834px" viewBox="0 0 16.721 93.834" enable-background="new 0 0 16.721 93.834" xml:space="preserve">
<path fill="#464646" d="M13.886,84.243L2.83,83.875c0,0,3.648-70.77,3.956-74.981C7.104,4.562,7.832,0,8.528,0
c0.695,0,1.752,4.268,2.053,8.894C10.883,13.521,13.886,84.243,13.886,84.243z"/>
<path fill="#464646" d="M16.721,85.475c0,4.615-3.743,8.359-8.36,8.359S0,90.09,0,85.475c0-4.62,3.743-8.363,8.36-8.363
S16.721,80.855,16.721,85.475z"/>
<circle fill="#EEEEEE" cx="8.426" cy="85.471" r="2.691"/>
</svg>
<input id="slider" type="range" min="25" max="100" step="25" value="0" />
<label id="lbl" id="value" for="">0%</label>
</div>
<script src="script.js"></script>
</body>
Can anyone suggest to me how to achieve this output. Any help will be appreciate.
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="65px" height="51.333px" viewBox="0 0 65 51.333" enable-background="new 0 0 65 51.333" xml:space="preserve">
<g>
<defs>
<rect id="SVGID_1_" x="-157.417" y="-176.417" width="648" height="864"/>
</defs>
<clipPath id="SVGID_2_">
<use xlink:href="#SVGID_1_" overflow="visible"/>
</clipPath>
<g clip-path="url(#SVGID_2_)">
<defs>
<rect id="SVGID_3_" x="-157.417" y="-176.417" width="648" height="864"/>
</defs>
<clipPath id="SVGID_4_">
<use xlink:href="#SVGID_3_" overflow="visible"/>
</clipPath>
</g>
<g clip-path="url(#SVGID_2_)">
<defs>
<rect id="SVGID_5_" x="-157.417" y="-176.417" width="648" height="864"/>
</defs>
<clipPath id="SVGID_6_">
<use xlink:href="#SVGID_5_" overflow="visible"/>
</clipPath>
</g>
<g clip-path="url(#SVGID_2_)">
<defs>
<rect id="SVGID_7_" x="-157.417" y="-176.417" width="648" height="864"/>
</defs>
<clipPath id="SVGID_8_">
<use xlink:href="#SVGID_7_" overflow="visible"/>
</clipPath>
<path clip-path="url(#SVGID_8_)" fill="#FFFFFF" d="M19.527,44.274c-1.653,5.015-7.059,7.738-12.071,6.085
c-5.015-1.654-7.739-7.058-6.087-12.071c1.652-5.015,7.058-7.739,12.072-6.084C18.456,33.855,21.179,39.26,19.527,44.274"/>
<path clip-path="url(#SVGID_8_)" fill="#58595B" d="M19.514,44.289c-1.652,5.013-7.056,7.736-12.066,6.083
c-5.014-1.653-7.736-7.056-6.084-12.067c1.652-5.013,7.055-7.736,12.068-6.083C18.443,33.875,21.166,39.277,19.514,44.289"/>
<path clip-path="url(#SVGID_8_)" fill="#FFFFFF" d="M17.318,43.564c-1.253,3.801-5.35,5.865-9.15,4.612
c-3.797-1.253-5.863-5.35-4.609-9.148c1.254-3.8,5.349-5.865,9.148-4.612C16.506,35.67,18.57,39.766,17.318,43.564"/>
<path clip-path="url(#SVGID_8_)" fill="#58595B" d="M19.266,34.944c2.242,3.113,2.33,7.115,0.57,10.255l45.249-43.24L9.721,31.148
C13.256,30.475,17.023,31.828,19.266,34.944"/>
<defs>
<filter id="Adobe_OpacityMaskFilter" filterUnits="userSpaceOnUse" x="6.52" y="-11.184" width="62.369" height="60.661">
<feFlood style="flood-color:white;flood-opacity:1" result="back"/>
<feBlend in="SourceGraphic" in2="back" mode="normal"/>
</filter>
</defs>
<mask maskUnits="userSpaceOnUse" x="6.52" y="-11.184" width="62.369" height="60.661" id="SVGID_9_">
<g filter="url(#Adobe_OpacityMaskFilter)">
<g enable-background="new ">
<g enable-background="new ">
<g>
<defs>
<path id="SVGID_10_" d="M17.516,29.352c2.243,0.335,3.028,2.134,3.028,2.134L57.891,6.807L17.516,29.352z"/>
</defs>
<clipPath id="SVGID_11_" clip-path="url(#SVGID_8_)">
<use xlink:href="#SVGID_10_" overflow="visible"/>
</clipPath>
<defs>
<filter id="Adobe_OpacityMaskFilter_1_" filterUnits="userSpaceOnUse" x="6.52" y="-11.184" width="62.369" height="60.661">
<feFlood style="flood-color:white;flood-opacity:1" result="back"/>
<feBlend in="SourceGraphic" in2="back" mode="normal"/>
</filter>
</defs>
<mask maskUnits="userSpaceOnUse" x="6.52" y="-11.184" width="62.369" height="60.661" id="SVGID_9_">
<g filter="url(#Adobe_OpacityMaskFilter_1_)">
<g enable-background="new ">
</g>
</g>
</mask>
<linearGradient id="SVGID_12_" gradientUnits="userSpaceOnUse" x1="-236.1787" y1="699.8169" x2="-235.1792" y2="699.8169" gradientTransform="matrix(36.5072 -22.3825 22.3825 36.5072 -7021.3823 -30804.627)">
<stop offset="0" style="stop-color:#FFFFFF"/>
<stop offset="0.0466" style="stop-color:#D5D7D8"/>
<stop offset="0.0983" style="stop-color:#B1B3B6"/>
<stop offset="0.1557" style="stop-color:#929497"/>
<stop offset="0.2174" style="stop-color:#797A7D"/>
<stop offset="0.2847" style="stop-color:#626366"/>
<stop offset="0.3595" style="stop-color:#4E4E50"/>
<stop offset="0.445" style="stop-color:#3B3B3C"/>
<stop offset="0.5476" style="stop-color:#202021"/>
<stop offset="0.6848" style="stop-color:#080809"/>
<stop offset="1" style="stop-color:#000000"/>
</linearGradient>
<polygon clip-path="url(#SVGID_11_)" mask="url(#SVGID_9_)" fill="url(#SVGID_12_)" points="28.547,49.477 68.889,24.744
46.861,-11.184 6.52,13.549 "/>
</g>
</g>
</g>
</g>
</mask>
<g opacity="0.8" clip-path="url(#SVGID_8_)">
<g>
<defs>
<rect id="SVGID_13_" x="6.52" y="-11.184" width="62.369" height="60.661"/>
</defs>
<clipPath id="SVGID_14_">
<use xlink:href="#SVGID_13_" overflow="visible"/>
</clipPath>
<g clip-path="url(#SVGID_14_)">
<defs>
<path id="SVGID_15_" d="M17.516,29.352c2.243,0.335,3.028,2.134,3.028,2.134L57.891,6.807L17.516,29.352z"/>
</defs>
<clipPath id="SVGID_16_">
<use xlink:href="#SVGID_15_" overflow="visible"/>
</clipPath>
<linearGradient id="SVGID_17_" gradientUnits="userSpaceOnUse" x1="-236.1787" y1="699.8169" x2="-235.1792" y2="699.8169" gradientTransform="matrix(36.5072 -22.3825 22.3825 36.5072 -7021.3823 -30804.627)">
<stop offset="0" style="stop-color:#FFFFFF"/>
<stop offset="0.1467" style="stop-color:#FFFFFF"/>
<stop offset="1" style="stop-color:#FFFFFF"/>
</linearGradient>
<polygon clip-path="url(#SVGID_16_)" fill="url(#SVGID_17_)" points="28.547,49.477 68.889,24.744 46.861,-11.184 6.52,13.549
"/>
</g>
</g>
</g>
<path clip-path="url(#SVGID_8_)" fill="#58595B" d="M12.709,42.044c-0.412,1.254-1.765,1.936-3.02,1.523
c-1.253-0.414-1.934-1.765-1.52-3.019c0.412-1.253,1.764-1.936,3.017-1.521C12.44,39.441,13.121,40.792,12.709,42.044"/>
</g>
</g>
</svg>
Here's a working SVG example, with a little bit of javascript.
It's not clear from your question what exactly you want the coloured divisions
to look like. For now I've just made them solid red. But you can make them look however you'd like by changing the fill of the <rect id="meter-colours" .../>.
let METER_DIVISIONS = 10;
let METER_DIVISIONS_GAP = 4;
// Initialise meter
let meterDivisionsElem = document.getElementById("meter-divisions");
// Calculate size of each meter division
let totalLen = meterDivisionsElem.getAttribute("r") * Math.PI;
let numGaps = METER_DIVISIONS - 1;
let divLen = (totalLen - METER_DIVISIONS_GAP * numGaps) / METER_DIVISIONS;
// Set meter divisions dash pattern
let pat = divLen + ' ' + METER_DIVISIONS_GAP + ' ';
meterDivisionsElem.setAttribute("stroke-dasharray", pat.repeat(numGaps) + ' ' + divLen + ' ' + totalLen);
// The slider form element
let sliderElem = document.getElementById("slider");
// The slider label element
let sliderLabelElem = document.getElementById("lbl");
// The needle element
let needleElem = document.getElementById("needle");
// The black bar element
let blackBarElem = document.getElementById("black-bar");
let blackBarLen = blackBarElem.getAttribute("r") * Math.PI;
// The meter colours element
let meterColoursElem = document.getElementById("meter-colours");
function setMeterTo(value)
{
// Update slider label
sliderLabelElem.textContent = value + '%';
// Update needle position
let rotation = value * 180 / 100;
needleElem.setAttribute("transform", "rotate(" + rotation + ")");
// Update black bar length
let barLen = value * blackBarLen / 100;
blackBarElem.setAttribute("stroke-dasharray", barLen + ' ' + (2 * blackBarLen));
// Update coloured division section of the meter
meterColoursElem.setAttribute("transform", "rotate(" + rotation + ")");
}
// Add an input change handler to the slider
sliderElem.addEventListener("input", function(evt) {
setMeterTo(evt.target.value);
});
// Initialise meter to start value
setMeterTo(25);
#wrapper {
width: 600px;
}
#slider {
width: 100%;
}
#needle {
stroke-linecap: round;
}
<div id="wrapper">
<svg id="meter" viewBox="0 0 200 110">
<defs>
<circle id="meter-divisions" r="90" stroke-width="20" stroke-dasharray="282.7"/>
<mask id="meter-divisions-mask">
<use xlink:href="#meter-divisions" stroke="white"/>
</mask>
</defs>
<g transform="translate(100,100) scale(1,-1)" fill="none">
<!-- The grey meter divisions -->
<use xlink:href="#meter-divisions" stroke="lightgrey"/>
<!-- The inner semicircular black bar -->
<circle id="black-bar" r="74" stroke="black" stroke-width="4" stroke-dasharray="232.5"/>
<!-- the needle -->
<line id="needle" x2="70" y2="0" stroke="black" stroke-width="2" stroke-dasharray="282.7"/>
<!-- The coloured form of the ,meter divisions that gets rotated into view with the needle.
It is initially off screen but gets revealed as it rotates. It is masked by using
a shape matching the gray divisions -->
<g mask="url(#meter-divisions-mask">
<rect id="meter-colours" x="-100" y="-100" width="200" height="100" fill="red"/>
</g>
</g>
</svg>
<input id="slider" type="range" min="25" max="100" step="25" value="25" />
<label id="lbl" id="value" for="">0%</label>
</div>
Update re new needle image
First let's simplify your needle image:
<svg width="650px" viewBox="0 0 65 51.333">
<g>
<path fill="#58595B" fill-rule="evenodd" d="M19.514,44.289c-1.652,5.013-7.056,7.736-12.066,6.083c-5.014-1.653-7.736-7.056-6.084-12.067c1.652-5.013,7.055-7.736,12.068-6.083C18.443,33.875,21.166,39.277,19.514,44.289 M17.318,43.564c-1.253,3.801-5.35,5.865-9.15,4.612c-3.797-1.253-5.863-5.35-4.609-9.148c1.254-3.8,5.349-5.865,9.148-4.612C16.506,35.67,18.57,39.766,17.318,43.564 M12.709,42.044c-0.412,1.254-1.765,1.936-3.02,1.523c-1.253-0.414-1.934-1.765-1.52-3.019c0.412-1.253,1.764-1.936,3.017-1.521C12.44,39.441,13.121,40.792,12.709,42.044"/>
<path fill="#58595B" d="M19.266,34.944c2.242,3.113,2.33,7.115,0.57,10.255l45.249-43.24L9.721,31.148C13.256,30.475,17.023,31.828,19.266,34.944"/>
<path fill="#FFFFFF" fill-opacity="0.8" d="M17.516,29.352c2.243,0.335,3.028,2.134,3.028,2.134L57.891,6.807L17.516,29.352z"/>
</g>
</svg>
To use this in your meter, we'll need to locate an accurate centre of rotation (the dot in the circle), and re-orient it so that it has zero angle. We may also need to adjust its scale to fit the meter.
By analysis and experimentation, the centre of rotation is at (10.44, 41.3) and the rotation needed to zero out the rotation is 35.8 deg.
Since the meter is centred at (0,0) we need to start by shifting and rotating the new needle by those amounts.
<svg width="650px" viewBox="0 0 65 51.333">
<g transform="rotate(35.8) translate(-10.44, -41.3)">
<path fill="#58595B" fill-rule="evenodd" d="M19.514,44.289c-1.652,5.013-7.056,7.736-12.066,6.083c-5.014-1.653-7.736-7.056-6.084-12.067c1.652-5.013,7.055-7.736,12.068-6.083C18.443,33.875,21.166,39.277,19.514,44.289 M17.318,43.564c-1.253,3.801-5.35,5.865-9.15,4.612c-3.797-1.253-5.863-5.35-4.609-9.148c1.254-3.8,5.349-5.865,9.148-4.612C16.506,35.67,18.57,39.766,17.318,43.564 M12.709,42.044c-0.412,1.254-1.765,1.936-3.02,1.523c-1.253-0.414-1.934-1.765-1.52-3.019c0.412-1.253,1.764-1.936,3.017-1.521C12.44,39.441,13.121,40.792,12.709,42.044"/>
<path fill="#58595B" d="M19.266,34.944c2.242,3.113,2.33,7.115,0.57,10.255l45.249-43.24L9.721,31.148C13.256,30.475,17.023,31.828,19.266,34.944"/>
<path fill="#FFFFFF" fill-opacity="0.8" d="M17.516,29.352c2.243,0.335,3.028,2.134,3.028,2.134L57.891,6.807L17.516,29.352z"/>
</g>
</svg>
The needle is now horizontal, and positioned at (0,0).
We can now integrate it into the previous solution:
let METER_DIVISIONS = 10;
let METER_DIVISIONS_GAP = 4;
// Initialise meter
let meterDivisionsElem = document.getElementById("meter-divisions");
// Calculate size of each meter division
let totalLen = meterDivisionsElem.getAttribute("r") * Math.PI;
let numGaps = METER_DIVISIONS - 1;
let divLen = (totalLen - METER_DIVISIONS_GAP * numGaps) / METER_DIVISIONS;
// Set meter divisions dash pattern
let pat = divLen + ' ' + METER_DIVISIONS_GAP + ' ';
meterDivisionsElem.setAttribute("stroke-dasharray", pat.repeat(numGaps) + ' ' + divLen + ' ' + totalLen);
// The slider form element
let sliderElem = document.getElementById("slider");
// The slider label element
let sliderLabelElem = document.getElementById("lbl");
// The needle element
let needleElem = document.getElementById("needle");
// The black bar element
let blackBarElem = document.getElementById("black-bar");
let blackBarLen = blackBarElem.getAttribute("r") * Math.PI;
// The meter colours element
let meterColoursElem = document.getElementById("meter-colours");
function setMeterTo(value)
{
// Update slider label
sliderLabelElem.textContent = value + '%';
// Update needle position
let rotation = value * 180 / 100;
needleElem.setAttribute("transform", "rotate(" + (-rotation) + ")");
// Update black bar length
let barLen = value * blackBarLen / 100;
blackBarElem.setAttribute("stroke-dasharray", barLen + ' ' + (2 * blackBarLen));
// Update coloured division section of the meter
meterColoursElem.setAttribute("transform", "rotate(" + rotation + ")");
}
// Add an input change handler to the slider
sliderElem.addEventListener("input", function(evt) {
setMeterTo(evt.target.value);
});
// Initialise meter to start value
setMeterTo(25);
#wrapper {
width: 600px;
}
#slider {
width: 100%;
}
#needle {
stroke-linecap: round;
}
<div id="wrapper">
<svg id="meter" viewBox="0 0 200 110">
<defs>
<circle id="meter-divisions" r="90" stroke-width="20" stroke-dasharray="282.7"/>
<mask id="meter-divisions-mask">
<use xlink:href="#meter-divisions" stroke="white"/>
</mask>
</defs>
<g transform="translate(100,100)">
<g transform="scale(1,-1)" fill="none">
<!-- The grey meter divisions -->
<use xlink:href="#meter-divisions" stroke="lightgrey"/>
<!-- The inner semicircular black bar -->
<circle id="black-bar" r="74" stroke="black" stroke-width="4" stroke-dasharray="232.5"/>
<!-- The coloured form of the ,meter divisions that gets rotated into view with the needle.
It is initially off screen but gets revealed as it rotates. It is masked by using
a shape matching the gray divisions -->
<g mask="url(#meter-divisions-mask">
<rect id="meter-colours" x="-100" y="-100" width="200" height="100" fill="red"/>
</g>
</g>
<!-- the needle -->
<g id="needle">
<g transform="rotate(35.8) translate(-10.44, -41.3)">
<path fill="#58595B" fill-rule="evenodd" d="M19.514,44.289c-1.652,5.013-7.056,7.736-12.066,6.083c-5.014-1.653-7.736-7.056-6.084-12.067c1.652-5.013,7.055-7.736,12.068-6.083C18.443,33.875,21.166,39.277,19.514,44.289 M17.318,43.564c-1.253,3.801-5.35,5.865-9.15,4.612c-3.797-1.253-5.863-5.35-4.609-9.148c1.254-3.8,5.349-5.865,9.148-4.612C16.506,35.67,18.57,39.766,17.318,43.564 M12.709,42.044c-0.412,1.254-1.765,1.936-3.02,1.523c-1.253-0.414-1.934-1.765-1.52-3.019c0.412-1.253,1.764-1.936,3.017-1.521C12.44,39.441,13.121,40.792,12.709,42.044"/>
<path fill="#58595B" d="M19.266,34.944c2.242,3.113,2.33,7.115,0.57,10.255l45.249-43.24L9.721,31.148C13.256,30.475,17.023,31.828,19.266,34.944"/>
<path fill="#FFFFFF" fill-opacity="0.8" d="M17.516,29.352c2.243,0.335,3.028,2.134,3.028,2.134L57.891,6.807L17.516,29.352z"/>
</g>
</g>
</g>
</svg>
<input id="slider" type="range" min="25" max="100" step="25" value="25" />
<label id="lbl" id="value" for="">0%</label>
</div>
My approach here is a pure CSS one ,i didn't use SVG and implementing JS code in this case is lot easier.
Output
var black = document.getElementsByClassName('black')[0];
var gradient = document.getElementsByClassName('gradient')[0]
var tick = document.getElementsByClassName('tick')[0];
var m = document.getElementById('m');
m.addEventListener('input', function() {
black.style.transform = gradient.style.transform = tick.style.transform = "translate(-50%,-50%)rotateZ(-" + (180 / 100) * m.value + "deg)"
});
* {
margin: 0px;
padding: 0px;
font-family: "arial";
}
.gauge_main,
.white,
.black,
.tick,
.gradient,
.chamber,
.meter {
position: absolute;
top: 50%;
left: 50%;
height: 280px;
width: 280px;
transform: translate(-50%, -50%);
transition: 0.4s;
}
.meter {
background-color: white;
height: 281px;
width: 281px;
border-radius: 50%;
clip-path: polygon(0 50%, 100% 50%, 100% 100%, 0 100%);
z-index: 1000;
}
.tick,
.chamber {
width: 160px;
height: 5px;
background: linear-gradient(to right, white 50%, black 50%);
z-index: 110;
transform-origin: 50%;
}
.tick {
z-index: 1001;
}
.chamber {
left: 50%;
background: white;
width: 280px;
height: 5px;
transform-origin: 50%;
transform: translate(-50%, -50%)rotateZ(calc(var(--i)*-20deg));
z-index: 20;
}
.white,
.black,
.gradient {
position: absolute;
background-color: black;
border-radius: 50%;
}
.white {
height: 180px;
width: 180px;
background-color: white;
z-index: 30;
}
.gradient {
height: 280px;
width: 280px;
background: linear-gradient(0deg, rgba(29, 216, 255, 1) 0%, rgba(4, 106, 255, 1) 50%, rgba(208, 212, 255, 1) 50%);
}
.black {
height: 200px;
width: 200px;
background: linear-gradient(to top, black 50%, rgba(208, 212, 255, 1) 50%);
box-shadow: 0px 0px 0px 5px white;
z-index: 25;
}
#keyframes load {
from {
transform: translate(-50%, -50%)rotateZ(0deg);
}
to {
transform: translate(-50%, -50%)rotateZ(-180deg);
}
}
input {
position: absolute;
top: 50%;
left: 50%;
width: 280px;
transform: translate(-50%, 50%);
}
<div class="gauge_main">
<div class="gradient"></div>
<div class="white"></div>
<div class="black"></div>
<div class="tick"></div>
<div style="--i:1" class="chamber"></div>
<div style="--i:2" class="chamber"></div>
<div style="--i:3" class="chamber"></div>
<div style="--i:4" class="chamber"></div>
<div style="--i:5" class="chamber"></div>
<div style="--i:6" class="chamber"></div>
<div style="--i:7" class="chamber"></div>
<div style="--i:8" class="chamber"></div>
<div style="--i:9" class="chamber"></div>
<div class="meter"></div>
</div>
<input type="range" id="m" name="meter" min="0" max="100" value="0">

How to make a random color function acting on svg groups call the same color for all elements in group

I have a function that creates a random color. I use this function in another function to call the color onclick of an svg groups. The problem is that all elements in the group get a random color, whilst it should be one random color for all elements in the group.
I have tried tweaking the function that calls the color. And I don't manage to tweak the random function properly.
function call1(){
const children = document.getElementById('btn1').children;
for(let i = 0; i < children.length; i++ ){
children[i].setAttribute('fill',getRandomColor());
}
}
function getRandomColor() {
var letters = '0123456789ABCDEF';
var color = '#';
for (var i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
function setRandomColor() {
$("#colorpad").css("background-color", getRandomColor());
}
#svg-object{
height: 100vh;
width: 100%;
background-size: cover;
background-position: center center;
border: 15px antiquewhite;
position: absolute;
}
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg id="svg-object" version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="800px" height="754px" viewBox="0 0 800 754" enable-
background="new 0 0 800 754" xml:space="preserve">
<g id="btn1" onclick="call1()">
<polygon fill="#FF0013" points="366.699,131 410,56 453.301,131 "/>
<polygon fill="#07FF00" points="323.699,656 367,581 410.301,656
"/>
<polygon fill="#0000FF" points="409.699,656 453,581 496.301,656
"/>
<polygon points="366.699,581 410,656 453.301,581 "/>
</g>
</svg>
I expect all the elements in the group to change to one random color.``
Instead of calling the getRandomColor() inside the loop, call it outside and set in a variable.
function call1() {
const children = document.getElementById('btn1').children;
let color = getRandomColor();
for (let i = 0; i < children.length; i++) {
children[i].setAttribute('fill', color);
}
}
function getRandomColor() {
var letters = '0123456789ABCDEF';
var color = '#';
for (var i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
function setRandomColor() {
$("#colorpad").css("background-color", getRandomColor());
}
#svg-object {
height: 100vh;
width: 100%;
background-size: cover;
background-position: center center;
border: 15px antiquewhite;
position: absolute;
}
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg id="svg-object" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="800px" height="754px" viewBox="0 0 800 754" enable- background="new 0 0 800 754" xml:space="preserve">
<g id="btn1" onclick="call1()">
<polygon fill="#FF0013" points="366.699,131 410,56 453.301,131"/>
<polygon fill="#07FF00" points="323.699,656 367,581 410.301,656"/>
<polygon fill="#0000FF" points="409.699,656 453,581 496.301,656"/>
<polygon points="366.699,581 410,656 453.301,581"/>
</g>
</svg>
function call1() {
console.log('call1');
const children = document.getElementById('btn1').children;
var randomColor = getRandomColor();
for (let i = 0; i < children.length; i++) {
children[i].setAttribute('fill', randomColor);
}
}
Your getRandomColor function is working well.
But, for all elements, we need call getRandomColor once.

equivalent in JS of the resize css applied to an SVG element

I recently discover the CSS resize:
`
div {
resize: both;
overflow: auto;
}
`
There is also a polyFill, but none of them won't work for a SVG element :/
Is it possible at least ?
Starting from your code and #ChrisG's comment, you might do the following.
When one clicks on svg, wrap this svg into a resizable div and set the svg width and height attributes to '100%' so the svg would get the wrapper's size.
Then, when one resizes the div, the svg would be resized as well.
When one clicks out of the div, set the svg width and height attributes explicitly and remove the div.
See the snippet below:
window.addEventListener('DOMContentLoaded', () => {
document.addEventListener('click', (e) => {
var r = document.getElementById('r');
if (r && r != e.target) {
var svg = r.querySelector('svg');
svg.setAttribute('width', r.offsetWidth + 'px');
svg.setAttribute('height', r.offsetHeight + 'px');
r.replaceWith(svg);
}
var svg = e.target;
while (!!svg && svg.nodeName != 'svg')
svg = svg.parentElement;
if (!svg) return;
var r = document.createElement('div');
r.id = 'r';
r.style.width = svg.width.baseVal.valueAsString;
r.style.height = svg.height.baseVal.valueAsString;
svg.setAttribute('width', '100%');
svg.setAttribute('height', '100%');
svg.parentElement.insertBefore(r, svg);
r.appendChild(svg.parentElement.removeChild(svg));
});
});
svg {
background: #cef
}
#r {
display: inline-block;
box-sizing: border-box;
border: solid 1px;
resize: both;
overflow: hidden;
}
<svg width="100px" height="100px" viewBox="0 0 100 100">
<circle stroke="navy" stroke-width="5" fill="pink" cx="50" cy="50" r="40"/>
</svg>
<svg width="100px" height="100px" viewBox="0 0 100 100">
<circle stroke="green" stroke-width="5" fill="gold" cx="50" cy="50" r="40"/>
</svg>

Can an SVG background be interactive?

I have an SVG image that I created. It is a rectangle with a circle inside it. The circle follows the users mouse using JavaScript. The image is represented by the following code:
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlspace="preserve" preserveAspectRatio="xMidYMin slic">
<style>
* { vector-effect:non-scaling-stroke }
rect { fill: blue; }
circle { fill:orange; opacity:0.75; }
</style>
<rect cx="50%" cy="0" width="720" height="1278" id="origin" />
<circle cx="50%" cy="116" r="72" id="dot" />
<script>
var svg = document.documentElement,
pt = svg.createSVGPoint(),
dot = document.querySelector('#dot');
svg.addEventListener('mousemove',function(evt){
var loc = cursorPoint(evt);
dot.setAttribute('cx',loc.x);
dot.setAttribute('cy',loc.y);
},false);
function rotateElement(el,originX,originY,towardsX,towardsY){
var degrees = Math.atan2(towardsY-originY,towardsX-originX)*180/Math.PI + 90;
el.setAttribute(
'transform',
'translate('+originX+','+originY+') translate('+(-originX)+','+(-originY)+')'
);
}
// Get point in global SVG space
function cursorPoint(evt){
pt.x = evt.clientX; pt.y = evt.clientY;
return pt.matrixTransform(svg.getScreenCTM().inverse());
}
</script>
</svg>
What I would like to do with this image is use it as a CSS background. If I use CSS to set the image as a background {background: url("image.svg");} then the JavaScript no longer works, i.e. the circle no longer follows the cursor. I believe this is due to the fact that when the image is a background, it has other elements stacked on top of it.
So how can I have the image be a background and also remain interactive? Any suggestions would be greatly appreciated.
One of the way to get a scripted background SVG working, is to use CSS4 element(). It's currently only implemented in Firefox 4+ via -moz-element().
An example:
<div id="bg" style="width: 400px; height: 400px;">
<svg width="400" height="400" viewPort="0 0 400 400">
<!-- force correct 0,0 coordinates -->
<rect x="0" y="0" width="1" height="1" fill="transparent" />
<rect x="0" y="0" id="animable1" width="120" height="120" fill="blue" />
<rect x="0" y="0" id="animable2" width="60" height="60" fill="red" />
</svg>
</div>
<div id="target" style="border: 4px dashed black; height: 400px; width: 400px; background: gray -moz-element(#bg); background-size: 20%;"></div>
<script type="text/javascript">
var divTarget = document.getElementById("target");
var animable1 = document.getElementById("animable1");
var animable2 = document.getElementById("animable2");
document.addEventListener("mousemove", function(event){
var rotation = Math.atan2(event.clientY, event.clientX);
animable1.setAttribute("transform", "translate(140 140) rotate(" + (rotation / Math.PI * 360) + " 60 60)");
animable2.setAttribute("transform", "translate(170 170) rotate(" + (360 - rotation / Math.PI * 360) + " 30 30)");
}, false);
animable1.setAttribute("transform", "translate(140 140) rotate(0 60 60)");
animable2.setAttribute("transform", "translate(170 170) rotate(0 30 30)");
</script>
You should create two files one of the .css file and the other one is Of course, better to have separate files, because it is actually a part of the container is html. SVG within this container,'s JavaScripts. This divide-and-conquer simplification or incident. For this reason, the external file is actually being held as JavaScripts. This is a great way to will not loose in the code.
SVG defining:
<div><object id="circle-svg" width="400" height="300" type="image/svg+xml" data="moving_circle.svg"></object></div>
Here, the part of the data you describe your own SVG
for example, canvas:
canvas = d3.select("#circle-svg")
.on("mouseover", mouseover)
.on("mousemove", mousemove)
.on("mouseout", mouseout);
Part1:
Take a look at this code (html based)
<head>
<title>Controlling an SVG with Javascript</title>
<script type='text/javascript' src='svg-interaction.js'></script>
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.js"></script>
<style type="text/css">
div.tooltip {
position: absolute;
text-align: center;
z-index: 10;
width: 140px;
height: auto;
padding: 8px;
font: 24px sans-serif;
background: red;
color: white;
border: solid 1px #aaa;
border-radius: 8px;
opacity: 0;
}
</style>
<head>
<body>
<h2>Controlling SVG with Javascript</h2>
<div class="page-content">
<div><object id="circle-svg" width="400" height="300" type="image/svg+xml" data="moving_circle.svg"></object></div>
You can define your scripts over there
Then you continue to the second phase (Your SVG study)
<svg viewBox="0 0 400 400" preserveAspectRatio="none"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:a3="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
a3:scriptImplementation="Adobe"
onload="init(evt)"
onzoom="updateTracker(evt)"
onscroll="updateTracker(evt)"
onresize="updateTracker(evt)">
<script type="text/ecmascript" a3:scriptImplementation="Adobe"><![CDATA[
/*****
*
* Globals
*
*****/
var elems = {
tracker: false,
cursor: false,
trans: true,
scale: true,
mx: true,
my: true,
ux: true,
uy: true
};
var frame = {
x_trans: 0,
y_trans: 0,
zoom : 1,
x_scale: 1,
y_scale: 1
};

Categories

Resources