I want to add multiple "drop-shadow" filters to an SVG, to achieve a 3D style effect, with the edges being a gradient -- similar to this (I'll later add an interval to animate it). I've used this to generate a gradient array with 70 shades.
However, I cannot get the "drop-shadow" property to work with my for-loop.
Unlike "text-shadow" style for example where I can do something like this:
var current = element.style.textShadow;
var appliedStyle = -i + 'px ' + i + 'px ' + gcolor[i];
element.style.textShadow = current+appliedStyle;
I couldn't figure out how to get the current "drop-shadow" property and concatenate another filter to it. So I came up with the idea to just add classes. This is my best attempt at coding it:
let text = document.querySelector(".brand-svg");
let gcolor = ["#ffb576","#ffb275","#ffb074","#ffad73","#ffab72","#ffa871","#ffa670","#ffa370","#ffa16f","#ff9e6e","#ff9c6d","#ff996c","#ff976b","#ff946a","#ff9269","#ff9068","#ff8d67","#ff8b66","#ff8865","#ff8664","#ff8363","#ff8163","#ff7e62","#ff7c61","#ff7960","#ff775f","#ff745e","#ff725d","#ff705c","#ff6d5b","#ff6b5a","#ff6859","#ff6658","#ff6357","#ff6156","#ff5e56","#ff5c55","#ff5954","#ff5753","#ff5452","#ff5251","#ff4f50","#ff4d4f","#ff4b4e","#ff484d","#ff464c","#ff434b","#ff414a","#ff3e49","#ff3c49","#ff3948","#ff3747","#ff3446","#ff3245","#ff2f44","#ff2d43","#ff2b42","#ff2841","#ff2640","#ff233f","#ff213e","#ff1e3d","#ff1c3c","#ff193c","#ff173b","#ff143a","#ff1239","#ff0f38","#ff0d37","#ff0a36"];
for (var i = 1; i <= 70; i++) {
text.classList.add("shadowlayer"+i);
var current = document.querySelector(".shadowlayer"+i);
current.style.setProperty(`-webkit-filter`, `drop-shadow 0px (${-i})px (${gcolor[i]})`);
};
body {
display: flex;
justify-content: center;
}
.brand-svg {
width: 400px;
}
<svg class="brand-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 748.17 433.89"><defs>
<style> .brand-svg path, polygon {fill:#fff;stroke:#ffb777;stroke-miterlimit:10;stroke-width:3px;}</style>
</defs>
<path class="svg-b-d" d="M746.37,26.21q-1.07-10.31-13.43-17.09T701.85,1.74c-1,0-2,0-3,0a142.7,142.7,0,0,0-37.5,5.51,200.39,200.39,0,0,0-43.71,18L559.68,57l86.6,47.6,57.94-31.84q21.79-12,32.51-24.13T746.37,26.21ZM694.43,46q-3.63,7.51-18.36,15.6l-10.41,5.73L627,46.1l10.4-5.72q14.73-8.1,28.39-10.09a51.29,51.29,0,0,1,7.33-.56,32,32,0,0,1,15.63,3.68Q698,38.51,694.43,46Z"/>
<polygon class="svg-b-n" points="539.34 112.27 583.62 136.62 493.18 137.64 449.59 161.6 536.18 209.2 579.78 185.24 536.24 161.31 625.93 159.87 669.53 135.91 582.93 88.31 539.34 112.27"/>
<path class="svg-b-a" d="M308.71,209.12l35.73,75.55,45.77-25.15-6.22-11,46.35-25.47,20,3.42,45.95-25.26L359,181.48Zm90.59,8.56-25.13,13.81-11.53-20.15Z"/>
<path class="svg-b-r" d="M326.64,290.81q1-8-7.66-12.75-8.07-4.43-20.48-4.66l-1.27,0q-11.94,0-26.85,4.31a160.09,160.09,0,0,0-32.6,13.82L173,327.12l86.6,47.61,43.6-24-30.88-17.18,57.2,2.72L377.4,310,314,308.37Q325.61,298.76,326.64,290.81Zm-47.71,17.27c-.91,1.81-3.46,3.85-7.65,6.16L254,323.74l-14.68-8.07,17.28-9.5a33.36,33.36,0,0,1,11.28-4.06,21.59,21.59,0,0,1,2.7-.18,12.06,12.06,0,0,1,5.93,1.37Q280.29,305.39,278.93,308.08Z"/>
<path class="svg-b-b" d="M186,353.17q-6.37-3.5-16.52-3.5c-1.16,0-2.37,0-3.61.1a84.43,84.43,0,0,0-27.06,6.61q8.9-6.78,10.07-13.14t-6.4-10.53a34,34,0,0,0-16.65-3.82,63.66,63.66,0,0,0-12.28,1.3q-17.93,3.53-39.14,15.19l-70.89,39L90.13,432,163,391.9q22.57-12.4,28.49-22.45T186,353.17ZM80.89,378.94l-12.24-6.73,16.9-9.28a33.73,33.73,0,0,1,10.27-4,15,15,0,0,1,2.45-.21,10.35,10.35,0,0,1,5.1,1.24c2,1.12,2.6,2.46,1.68,4.05s-3.35,3.45-7.27,5.61Zm58.23.6q-1.44,2.34-7.33,5.58L112,396,99.72,389.3l19.84-10.91q7.53-4.14,12.81-4.14a10.27,10.27,0,0,1,5,1.2C139.5,376.62,140.09,378,139.12,379.54Z"/></svg>
Why doesn't this work? Is there an easier way to do this?
The filter property accepts multiple filters, so you can repeat drop-shadow:
filter: drop-shadow(30px 10px 4px #44d) drop-shadow(70px 1px 4px #f0d);
All you need to do is pass correct string to style property filter.
In your case code would look something like this:
let text = document.querySelector(".brand-svg");
let gcolor = ["#ffb576","#ffb275","#ffb074"];
for (var i = 0; i < gcolor.length; i++) {
text.style.filter = `${text.style.filter} drop-shadow(0px ${-i}px 0px ${gcolor[i]})`;
}
I want to change an input value by clicking on a div + adding a CSS effect . The issue is that I got 10 divs. I can do it by making a function for each one of them but i´m pretty sure that there is a much better way to do it without repeating the code. I´m starting at this, so it would be great if i could get some help.
I got an input and a few divs with just numbers:
<input type="text" id="screen" dir="rtl"/>
<div class="key" id="tres">3</div>
<div class="key" id="dos">2</div>
<div class="key" id="uno">1</div>
...
And then the function:
var screen = document.getElementById("screen");
var tres = document.getElementById("tres");
var dos = document.getElementById("dos");
var uno = document.getElementById("uno");
uno.onclick=function to () {
screen.value+=uno.innerHTML;
uno.style.boxShadow="none";
setTimeout(function() {
uno.style.boxShadow="0px 0px 5px 2px rgba(0, 0, 0, 0.2), inset 0px 0px 1px 1px rgba(0, 0, 0, 0.2)";
}, 220);
}
Now what i want to do is to use the same ".onclick" function on every single div, not in my newbie way (re writting the function and changing variables) but in a practical one. I've been trying with "classname" instead of "id". I´ve attempted with ".replace" by trying to replace the value names. And actually im trying with something like this:
var hell=["uno","dos","tres"];
var screen = document.getElementById("screen");
for (var i = 0; i < hell.length; i++) {
document.getElementById(hell[i]).onclick=function to() {
screen.value+=document.getElementById(hell[i]).innerHTML;
}
}
I barely know what I´m doing so a little bit of enlightenment would be grateful (avoiding jQuery if possible).
Thanks a lot! (Sorry about my bad English)
You can simply select the elements by class name, attach an eventListener to each of the elements, and you'll get what you wanted.
var screen = document.getElementById("screen");
var elements = Array.from(document.getElementsByClassName('key'));
elements.forEach(el => el.addEventListener('click', fn));
function fn(e) {
screen.value = (parseInt(screen.value, 10) || 0) + parseInt(e.target.innerText, 10);
elements.forEach(el => el.style.boxShadow = "none");
setTimeout(function() {
e.target.style.boxShadow = "0px 0px 5px 2px rgba(0, 0, 0, 0.2), inset 0px 0px 1px 1px rgba(0, 0, 0, 0.2)";
}, 220);
}
<input type="text" id="screen" dir="rtl" />
<div class="key" id="tres">3</div>
<div class="key" id="dos">2</div>
<div class="key" id="uno">1</div>
I think what you're looking for is querySelectorAll. Basically, instead of getting each one by ID, you can get all of them in an array by getting them by their class (since they all have the same class).
var myInput = document.getElementById('screen');
var inputs = document.querySelectorAll('.key');
inputs.forEach(function(input) {
input.onclick = function() {
myInput.value += input.innerHTML;
// everything else goes here
}
});
See this pen for a working example.
ES6
Here is a more optimal solution to your problem. It may seem a bit obscure if you don't know new ES6 syntax, but since you want to learn anyway, here it goes:
let screenValue = 0,
lastClickedDiv;
const screen = document.getElementById('screen'),
keys = [...document.getElementsByClassName('key')];
const toggleBoxShadow = (clickedDiv) => {
if (clickedDiv !== lastClickedDiv) {
clickedDiv.classList.add('active');
if (lastClickedDiv !== undefined) {
lastClickedDiv.classList.remove('active');
}
lastClickedDiv = clickedDiv;
}
};
const keysClickListener = (event) => {
screenValue += parseInt(event.target.textContent);
screen.value = screenValue;
setTimeout(() => toggleBoxShadow(event.target), 220);
};
keys.forEach(key => key.addEventListener('click', keysClickListener));
.key.active {
box-shadow: 0 0 5px 2px rgba(0, 0, 0, 0.2),
inset 0 0 1px 1px rgba(0, 0, 0, 0.2);
}
<input type="text" id="screen" dir="rtl"/>
<div class="key">3</div>
<div class="key">2</div>
<div class="key">1</div>
[...document.getElementsByClassName('key')] gets all elements with class name key and turns that collection into an array using spread operator.
keys.forEach(key => key.addEventListener('click', keysClickListener)) adds click listener named keysClickListener to each element of keys array.
keysClickListener function updates screenValue counter by adding number from clicked div to value it already stores and sets that as #screen's value. Then it invokes toggleBoxShadow() after 220 ms.
If you want anything else explained, let me know.
I wan't to make show() and hide() method like jquery have, but with pure javascript because I want to modify how the element show and hide. But after my attempts, I've changed where the code placed, changed the code, etc, still it won't work. Only for a few times it was work but it was inconsistent (when I try to run it through firefox, it work for once but never again). The display (block and none) and the exact width and height is work, but not the 2s transition. How to fix this?
<!doctype html>
<html>
<head>
<style>
div {
width: 0px;
height: 0px;
background-color: yellow;
border: 1px solid black;
display: none;
}
</style>
</head>
<body>
<div>
</div>
<button>
click !
</button>
<script>
var x = document.getElementsByTagName("button");
var y = document.getElementsByTagName("div");
x[0].onclick = fungsi;
function fungsi() {
if (y[0].style.display != "block") {
y[0].style.display = "block";
y[0].style.transition = "width 2s";
y[0].style.transition = "height 2s";
y[0].style.width = "100px";
y[0].style.height = "100px";
} else {
y[0].style.display = "";
y[0].style.transition = "width 2s";
y[0].style.transition = "height 2s";
y[0].style.width = "";
y[0].style.height = "";
}
};
</script>
</body>
</html>
I've messed around with your code and found that the reason for your problem was the switching from display: none; to display: block; and back. I've made a simple solution for this if you would like to use it.
Here is the modified CSS code.
div {
width: 0px;
height: 0px;
background-color: yellow;
border: none;
}
Here is the modified JS code.
var x = document.getElementsByTagName("button");
var y = document.getElementsByTagName("div");
x[0].onclick = fungsi;
var expanded = false;
function fungsi() {
y[0].style.transition = "all 2s";
if (!expanded) {
y[0].style.border = "1px solid black";
y[0].style.width = "100px";
y[0].style.height = "100px";
expanded = true;
} else {
var applyBorder = function () {
y[0].style.border = "none";
};
y[0].style.width = "0";
y[0].style.height = "0";
expanded = false;
setTimeout(applyBorder, 2000);
}
};
And here is a JSFiddle of this code for an example.
It could be something to do with vendor prefixes
So as well as having the following:
y[0].style.transition
You will also need:
y[0].style.mozTransition
y[0].style.webkitTransition
Try that, hopefully should work.
What you are doing is not animating show and hide with "pure javascript", it really is animating show and hide with CSS3. You are just setting the CSS properties trough javascript!
That being said, CSS3 transitions are not supported by every browser. For example even IE9 does not support it. Some other browser only work with prefixed versions of this property.
Try setting -moz-transition, -webkit-transition and -o-transition too.
For more details see: Transition browser support
However if you expect your animation to work across all major platforms I suggest you to use jQuery and try adjusting the settings to your desired behavior..
When you run
y[0].style.transition = "width 2s";
y[0].style.transition = "height 2s";
The first line will be overwritten by the second. So the transition goes only for width or height at a time, and when one of them is 0, the transition will be invisible.
You should set transition like this:
y[0].style.transition = 'width 2s,height 2s';
Or just set it for all properties that support transition:
y[0].style.transition = 'all 2s';
BTW, since the transition property is not changing, you should set them outside the changing part.
Another problem is, the <div> must be visible before the animation starts, otherwise it will have the desired width and height once become visible, and no transition is needed any more. visibility is another choice since an element with visibility: hidden still takes the place.
Here is a working copy of code:
<!doctype html>
<html>
<head>
<style>
div {
width: 0px;
height: 0px;
background-color: yellow;
border: 1px solid black;
/* Use visibility instead of display */
visibility: hidden;
}
</style>
</head>
<body>
<div>
</div>
<button>
click !
</button>
<script>
var x = document.getElementsByTagName("button");
var y = document.getElementsByTagName("div");
x[0].onclick = fungsi;
// set transition
y[0].style.transition = "width 2s,height 2s";
function fungsi() {
if (y[0].style.visibility != "visible") {
y[0].style.visibility = "visible";
y[0].style.width = "100px";
y[0].style.height = "100px";
} else {
y[0].style.visibility = "";
y[0].style.width = "";
y[0].style.height = "";
}
};
</script>
</body>
</html>
And something more, if you want to hide the element after the animation ends, setTimeout will work.