I have the following code in a tiny web page:
<svg width='200' height='200'>
<svg x=0 y=0>
<circle cx=50 cy=50 r=40 stroke='#808080' stroke-width=3 fill='#FF0000'/>
<circle cx=150 cy=50 r=40 stroke='#808080' stroke-width=3 fill='#00FF00'/>
<circle cx=50 cy=150 r=40 stroke='#808080' stroke-width=3 fill='#0000FF'/>
<circle cx=150 cy=150 r=40 stroke='#808080' stroke-width=3 fill='#FFFF00'/>
</svg>
<svg id='CtrBtn' x=0 y=0>
<circle cx=100 cy=100 r=20 stroke='#808080' stroke-width=3 fill='#000000'/>
</svg>
</svg>
<div id='status'>STATUS</div>
<script type='text/javascript'>
window.onload = btnHandler
function btnHandler() {
let divCtrBtn = document.getElementById('CtrBtn')
divCtrBtn.onclick = function() {
document.getElementById('status').innerHTML = 'Center-Button-Hit'
}
}
</script>
It works as I expect showing this for start:
And then this once I click the black button in the middle:
But this is what I want instead when clicking the button:
The red disk should change color to become cyan (#00FFFF) and the green should change color to become magenta (#FF00FF).
How should I change the code of the function btnHandler() to get this result ?
CodePudding user response:
You need to target the fill
attribute. In vanilla JS this is done with the setAttribute
method.
EDIT:
You also need a way to target them, I've just given an id
to each circle...
Just add those two extra lines in (see snippet below)
window.onload = btnHandler
function btnHandler() {
let divCtrBtn = document.getElementById('CtrBtn')
divCtrBtn.onclick = function() {
document.getElementById('status').innerHTML = 'Center-Button-Hit'
// extra lines here
document.getElementById('circle1').setAttribute('fill', '#00FFFF')
document.getElementById('circle2').setAttribute('fill', '#FF00FF')
}
}
<svg width='200' height='200'>
<svg x=0 y=0>
<circle id="circle1" cx=50 cy=50 r=40 stroke='#808080' stroke-width=3 fill='#FF0000'/>
<circle id="circle2" cx=150 cy=50 r=40 stroke='#808080' stroke-width=3 fill='#00FF00'/>
<circle id="circle3" cx=50 cy=150 r=40 stroke='#808080' stroke-width=3 fill='#0000FF'/>
<circle id="circle4" cx=150 cy=150 r=40 stroke='#808080' stroke-width=3 fill='#FFFF00'/>
</svg>
<svg id='CtrBtn' x=0 y=0>
<circle id="centreCirle"cx=100 cy=100 r=20 stroke='#808080' stroke-width=3 fill='#000000'/>
</svg>
</svg>
<div id='status'>STATUS</div>
CodePudding user response:
While you've already accepted an answer, I'll post an alternative that may also be useful to you along with explanatory comments in the JavaScript to explain how it works:
/* a relatively simple function to convert a string into chunks of size 'size': */
const chunk = (str, size) => {
/* establishing how many chunks we need: */
const parts = Math.ceil(str.length / size),
// converting the string into an Array, using String.prototype.split()
// with an empty-string passed as the argument:
temp = str.split('');
// we return the result of this expression; first we create a new Array
// using Array.from():
return Array.from({
// defining the length of the created Array:
length: parts
// using Array.prototype.map() to iterate over the created Array, and
// create a new array:
}).map(
// here we take a substring of Array-elements from the temp Array
// using splice, to take the elements between index 0 and a length
// represented by the size; we use Array.prototype.splice() because
// that modifies the Array on which it operates, allowing us to take
// from index 0 every iteration; we then join the array-elements
// together using Array.prototype.join():
() => temp.splice(0, size).join('')
);
},
// a simple function to handle the colour-cycling, since your colour modifications
// are basically an inversion, FF00FF to 00FF00 (as an example):
cycleColor = (el) => {
// here we get the current 'fill' attribute-value of the element, and replace
// all characters in the attribute-value that are not in the hex range of
// 0-f with an empty space; hence '#FF00FF' becomes 'FF00FF'; this string
// is then passed to the chunk function, and split into an Array of parts
// each a size of 2:
let current = chunk(el.getAttribute('fill').replace(/([^0-9a-f])/ig, ''), 2),
// the updated colour is created by iterating over the current colour and
// uses Array.prototype.map() to create a new Array:
updated = current
.map(
// color is a reference to the current hex colour, and from the
// original string 'FF00FF' will become an Array of values ['FF', '00', 'FF']
// each of those hex-values will be referenced in turn. Here we
// subtract the parsed Integer of the color (from base 16) from
// 255 (in the rgb colour-space 255 is the maximum value), and
// we convert that back into a hexadecimal representation using
// Number.prototype.toString(16):
(color) => (255 - parseInt(color, 16)).toString(16))
// because the above use of map allows for 0 to be returned this would
// result in an invalid hex-value:
.map((color) => {
// here, if the length of the color is 2 ('FF' for example),
// we return that color; otherwise (in the case of '0') we
// pad the String by placing a 0 at the front of the string:
return color.length === 2 ? color : '0' color;
})
// we then join the Array-elements together with an empty-string:
.join('');
// and then we use Element.setAttrbute() to update the property to
// the new colour string, using a template-literal to concatenate
// a '#' character:
el.setAttribute('fill', `#${updated}`);
},
// defining your original function as an Arrow function:
btnHandler = () => {
let divCtrBtn = document.getElementById('CtrBtn')
// revising your event-binding to use EventTarget.addEventListener()
// to bind the anonymous function to the 'click' event fired on the
// divCtrBtn element:
divCtrBtn.addEventListener('click', (evt) => {
// there was no obvious reason to add an attribute to identify the
// elements to update, since CSS could already target them; here
// we use document.querySelectorall() to find all <circle> elements
// in the range of -n 2 (the first two elements) that are found
// inside of an <svg> without an [id] attribute; we then use
// NodeList.prototype.forEach() to iterate over the retrieved elements:
document.querySelectorAll('svg:not([id]) > circle:nth-child(-n 2)').forEach(
// and call the cycleColor function on each of them:
(el) => cycleColor(el)
);
document.getElementById('status').innerHTML = 'Center-Button-Hit'
});
}
// as above, I changed the 'onload' event-binding to use the
// EventTarget.addEventListener() method, and call the btnHandler()
// function in response to the 'DOMContentLoaded' event:
window.addEventListener('DOMContentLoaded', (e) => {
btnHandler();
});
<svg width='200' height='200'>
<svg x=0 y=0>
<circle cx=50 cy=50 r=40 stroke='#808080' stroke-width=3 fill='#FF0000' />
<circle cx=150 cy=50 r=40 stroke='#808080' stroke-width=3 fill='#00FF00' />
<circle cx=50 cy=150 r=40 stroke='#808080' stroke-width=3 fill='#0000FF' />
<circle cx=150 cy=150 r=40 stroke='#808080' stroke-width=3 fill='#FFFF00' />
</svg>
<svg id='CtrBtn' x=0 y=0>
<circle cx=100 cy=100 r=20 stroke='#808080' stroke-width=3 fill='#000000' />
</svg>
</svg>
<div id='status'>STATUS</div>
References:
Array.prototype.forEach()
.Array.prototype.from()
.Array.prototype.join()
.Array.prototype.map()
.Array.prototype.splice()
.- Arrow functions.
- Conditional operator.
document.querySelectorAll()
.Element.getAttribute()
.Element.setAttribute()
.EventTarget.addEventListener()
.Number.prototype.toString()
.parseInt()
.String.prototype.split()
.- Template literals.