Finding width of hidden element similar to jQuery in pure javascript - javascript

I am trying to replicate the jQuery width function using pure JavaScript. Pure JavaScript functions such as getComputedStyle or offsetWidth seem to work if the element is visible, but I cannot replicate the same behavior when the element is hidden.
It seems that jQuery is doing something different here and I cannot figure out what exactly.
To clearly explain what i am trying to do, Here is a codepen example where I try the getComputedStyle in comparison with the jQuery width function for calculating the width of a hidden element that is changing dynamically.
const input = $('input');
const sizer = $('.sizer');
const sizerDom = document.querySelector('.sizer');
input.on('input', evt => {
sizer.text(evt.target.value);
console.log(sizer.width())
console.log(getComputedStyle(sizerDom, null).width);
});
.sizer {
display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input type="text">
<span class="sizer">
https://codepen.io/OmranAbazid/pen/OJNXyoG

That is because in jQuery's internal logic, it interally swaps the display property from none to another value so that it forces the browser to momentarily render it. Otherwise the measurements will always be 0 since the element is never rendered.
Also, instead of trying to use window.getComputedStyle which will return a string value of the CSS dimension (e.g. 100px), you can use sizerDom.getBoundingClientRect() to get the actual number instead (which returns, say, 100) without needing to do additional parsing.
const input = $('input');
const sizer = $('.sizer');
const sizerDom = document.querySelector('.sizer');
input.on('input', evt => {
sizer.text(evt.target.value);
console.log(sizer.width())
const cachedDisplay = window.getComputedStyle(sizerDom).display;
sizerDom.style.display = 'inline-block';
console.log(sizerDom.getBoundingClientRect().width);
sizerDom.style.display = cachedDisplay;
});
.sizer {
display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input type="text">
<span class="sizer">

Related

remove styles from all nodelist element and add only to clicked element Vanilla JS

I have multiple divs that when clicked adds a border and scales them up a little. I am looping through all elements using foreach and on click i remove every element's border and scale property except the clicked element, to which i add a border and scale.
My code is completely logical and is supposed to work but for some reason i cant seem to grasp, it only applies the styles to clicked elements but not removing from the rest of the elements (like my code says it should).
JS
document.querySelectorAll('.projcolorpick div').forEach(el => {
el.onclick = (e) => {
el.style.border = "none"
el.style.transform = "scale(1)"
e.target.style.border = "2px solid #fff"
e.target.style.transform = "scale(1.2)"
projcolor = e.target.style.background
}
})
}
give something like this a try... each element needs an id attribute for this to work (the filter part - if there is a unique attribute...)
const list = Array.from(document.querySelectorAll('.projcolorpick div'));
list.forEach(el => {
el.addEventListener('click', (e) => {
//code that affects the element you click on
el.style.border = "2px solid #fff"
el.style.transform = "scale(1.2)"
projcolor = e.target.style.background;
list.filter(x=>x.id!=el.id).forEach(otherEl=>{
//code that affects the other elements you didn't click on
otherEl.style.border = "none"
otherEl.style.transform = "scale(1)"
});
});
});
```
edit:
fixed some typos.
forEach only applies to Arrays unless you configure it otherwise.
querySelectorAll does not return arrays, but array-like objects (NodeLists)
To allow looping over NodeLists, add the following code:
if (window.NodeList && !NodeList.prototype.forEach) {
NodeList.prototype.forEach = Array.prototype.forEach;
}
var nL = document.querySelectorAll('*');
console.log(nL instanceof NodeList); // true
You don't really need an id attribute on each div and I would advocate using class-assignments instead of changing their individual attributes. You can compare the actual DOM elements with each other like c==ev.target, as you can see in my code below:
// === populate the page first ... ============================= START =
const cont=document.getElementById('container');
cont.innerHTML=
[...Array(3)].map(cp=>'<div class="projcolorpick">'+
[...Array(8)].map(d=>{
let hsl= "hsl("+Math.floor(Math.random()*360)+",100%,80%)";
return ' <div style="background-color:'+hsl+'">'+hsl+'</div>'}).join('\n')
+'</div>').join('\n');
// === populate the page first ... =============================== END =
// now, do the action:
cont.onclick=ev=>{
if ( ev.target.parentNode.classList.contains('projcolorpick')
&& ev.target.tagName=='DIV'){
[...ev.target.parentNode.children].forEach(c=>c.classList.toggle('selected',c==ev.target));
ev.target.parentNode.style.backgroundColor=ev.target.textContent;
}
}
.projcolorpick {border: 2px solid #888}
.selected {border: 2px solid #fff; transform:scale(1.2);}
div {margin:6px; padding:4px}
.projcolorpick div {width:200px; height:20px}
<div id="container"></div>
The action happens here:
cont.onclick=ev=>{
if ( ev.target.parentNode.classList.contains('projcolorpick')
&& ev.target.tagName=='DIV'){
[...ev.target.parentNode.children].forEach(c=>c.classList.toggle('selected',c==ev.target));
ev.target.parentNode.style.backgroundColor=ev.target.textContent;
}
}
I use a delegated event-attachment to the parent .container div. The first if statements makes sure that only clicks on .projcolorpick>div elements are processed.
If you want to include more than one generation between them you need to use something like ev.target.closest('.projcolorpick') instead ...
Now, inside the if block two things happen:
Using toggle() on all DOM elements in ev.target.parentNode.children the class "selected" is either
assigned or
removed.
The text found in the clicked div is applied as background-color to the parent .projcolorpick container.

Is there a way to point to document.styleSheets[].cssRules[] using a rule name?

I want to change a specific class-property-value using Javascript, but by the class-name itself and not with any integer as a pointer (cssRules[i]) or looping all classes to find the matching "selectorText" value.
This is for changing the readable on-screen language within the page.
<style id="languages" class="languages" title="languages">
<!--
/* ... more styles ... */
.lang-ita { display : none; }
.lang-eng { display : none; }
/* ... more styles ... */
-->
</style>
<script language="javascript">
<!--
function fxSwitchLanguage(i)
{
/* ... more code ... */
document.getElementById('languages').sheet.cssRules[i].style.setProperty('display','block');
/* ... more code ... */
}
-->
</script>
<button onClick="fxSwitchLanguage(0);">ITA</button>
<button onClick="fxSwitchLanguage(1);">ENG</button>
<br>
<div class="lang-ita">CIAO!</div>
<div class="lang-eng">HELLO!</div>
Of course I set the previous language "display" to "none" before showing only the new selected one.
I would like to have ".cssRules['.lang-eng']" instead of ".cssRules[i]".
Since this document is shared and may be changed by someone else, I really DO prefer to point the class using its name and not any hard-coded integer for obvious stability reasons, moreover I do not want to use a "for" cycle to test the "selectorText" property of each written class (can be easily thousands).
I don't mind any Browsers differences (.cssRules or .rules).
I just want to know if it is possible to have it in the way I'd prefer to.
No, there is no CSSOM API to get a rule (or list of rules) by its selector string. Iterating them to find one is the only way. Of course you wouldn't do this in the fxSwitchLanguage function, everytime it is called, but outside of it, only once when the script is loaded. Then just store references to the relevant rules in a few constants, or a data structure.
But since your goal is to manipulate the rules by JavaScript, I'd go even further and also create them using javascript. That way, you can easily store a reference to them without iteration.
const {sheet} = document.getElementById('languages');
const rules = new Map(['eng', 'ita', 'esp'].map(lang => {
const rule = sheet.cssRules[sheet.insertRule(`.lang-${lang} {
display: none;
}`)]; // yes, it's weird, `insertRule` returns an index
return [lang, rule];
}));
let active = null;
function fxSwitchLanguage(l) {
if (active) rules.get(active).style.display = 'none';
rules.get(l).style.display = 'block';
active = l;
} // or build a toggle or whatever
<style id="languages">
</style>
<button onClick="fxSwitchLanguage('ita');">ITA</button>
<button onClick="fxSwitchLanguage('eng');">ENG</button>
<button onClick="fxSwitchLanguage('esp');">ESP</button>
<br>
<div class="lang-ita">CIAO!</div>
<div class="lang-eng">HELLO!</div>
<div class="lang-esp">HOLA!</div>
from comment here is the idea :
function fxSwitchLanguage(varlang) {
//* test if already created and remove it before update*/
if (document.contains(document.getElementById("langSetting"))) {
document.getElementById("langSetting").remove();
}
//* update language chosen*/
var newStyle = document.createElement("style");
newStyle.setAttribute("id", "langSetting"); //* put a mark on it */
var addContent = ".lang-" + varlang + "{display:block;}";
newStyle.appendChild(document.createTextNode(addContent));
var head = document.getElementsByTagName("head")[0];
head.appendChild(newStyle);
}
[class^='lang'] {display:none;}
<button onClick="fxSwitchLanguage('ita');">ITA</button>
<button onClick="fxSwitchLanguage('eng');">ENG</button>
<hr>
<p class="lang-ita">CIAO!</p>
<div class="lang-eng">HELLO!</div>

Revert `display: none` on elements to original value [duplicate]

This question already has answers here:
how to revert back to normal after display:none for table row
(9 answers)
Closed 4 years ago.
explanation
I have a script that hides elements, if the user does not have the permission to view/use it. I set display: none for those elements.
Now later, when I want to show the elements again, I don't just want to set display: block or something, because maybe the elements original display value was something other than block. Is there a way I can revert the display or set it to a neutral value?
example
E.g. <div class="fancy-class">...</div> If fancy-class has display set to inline-block and I just set it to block with my script, that will break the ui.
attempts
I have tried using display: initial but that resets it to the HTML-element's initial styling - not the class's styling.
I hope I don't have to keep the original values in an array and then apply them again. Doesn't seem nice.
use: element.style.display = "" to reset style.display of an element
(() => {
const displayState = reset =>
Array.from(document.querySelectorAll("div"))
.forEach( el => el.style.display = reset ? "" : "none" );
// ^ restore original display state
document.querySelector("#showAll").addEventListener("click", () => displayState(true));
document.querySelector("#hideAll").addEventListener("click", () => displayState());
})();
#first, #second, #third {
display: inline-block;
margin-right: 1em;
}
<div id="first">[First]</div>
<div id="second">[Second]</div>
<div id="third">[Third]</div>
<button id="showAll">show divs</button>
<button id="hideAll">hide divs</button>
Before setting display to none, you can save it, and apply it back.
var display = document.getElementsByClassName("fancy-class").style.display;
document.getElementById("fancy-class").style.display = none;
Set above saved attribute.
document.getElementById("fancy-class").style.display = display;

Passing element to new ResizeObserver

I am trying to figure out a way to detect when a div element is resized and resize several other associated elements in the same class as it. I learned that the onresizeevent only works on the body but not individual elements.
I did discover ResizeObserver and there was this example ->How to detect DIV's dimension changed?
<html>
Width: <output id="width">0</output><br>
Height: <output id="height">0</output><br>
<textarea id="textbox">Resize me</textarea><br>
</html>
<script>
function outputsize() {
width.value = textbox.offsetWidth
height.value = textbox.offsetHeight
}
outputsize()
new ResizeObserver(outputsize).observe(textbox)
</script>
As you can see the observer is passed the object by name and a call back, but I am trying to figure out way to pass the call back an element. The example above the specific the element name (textbox) is in the function, but I'd like to pass it as an input variable. something like...
new ResizeObserver(outputsize(self)).observe(self)
I feel like this should be easy but I am missing it.
looking at the documentation page, I found an answer.
I define the interaction with elements when I define the resize observer.
var ro=new ResizeObserver( entrie => {
for (let entry of entrie){
outputsize(entry.target);
}
});
then I can set an observer using this for each element I want watched.
ro.observe(textbox);
ro.observe(t2);
So it all working together looks like this (this program resizes the bottom window to match how the top on is being resize).
<html>
Width: <output id="width">0</output><br>
Height: <output id="height">0</output><br>
ID:<output id="id"></output><br>
ID2:<output id="id2"></output><br>
<textarea id="textbox" class='tim'>Resize me</textarea><br>
w:<output id="w2">0</output><br>
h:<output id="h2">0</output><br>
<textarea id="t2" class='tom'>I'm #2</textarea><br>
<textarea id="t3" class='tom tim'>I'm gonna change</textarea><br>
</html>
<script>
function outputsize(ele) {
//console.log(ele);
id2.value=textbox;
width.value = ele.offsetWidth;
height.value = ele.offsetHeight;
var cl=document.getElementsByClassName(ele.classList[0]);
console.log(cl);
console.log(ele.classList);
for (x=0;x<cl.length;x++){
cl[x].style.width=ele.offsetWidth;
console.log(ele.id+" "+ele.offsetWidth);
console.log(cl[x].id+" "+cl[x].offsetWidth);
}
}
var ro=new ResizeObserver( entrie => {
for (let entry of entrie){
outputsize(entry.target);
}
});
ro.observe(textbox);
ro.observe(t2);
</script>
Your question just came up while I was searching for a solution for the same problem.
Here's what I ended up doing.
You can't do this
const element = document.querySelector(".foo");
new ResizeObserver(outputsize(element)).observe(element)
instead, you should do this
const element = document.querySelector(".foo");
new ResizeObserver(() => {
outputsize(element);
}).observe(element);

How to set input:focus style programatically using JavaScript

I'm building a UI library in JS that can, without relying on any CSS stylesheets, create UI components, stylised from code. So far, it's been quite easy, with exception of styling different control states (such as input:focus one).
Code that I use to create input field:
function newInput()
{
var ctr = docmuent.createElement("input");
ctr.setAttribute("type","text");
ctr.setAttribute("value", some-default-value);
ctr.style.fontFamily = "sans-serif,helvetica,verdana";
/* some font setup here, like color, bold etc... */
ctr.style.width = "256px";
ctr.style.height = "32px";
return ctr;
}
Styling it for default state is easy. However I am unsure how to set style for states such as focused, disabled or not-editable.
If I'd be having CSS stylesheets included in the project that would be easily sorted out. However I can't have any CSS files included, it must be pure JS.
Does anyone know how to set style for an input field state (eg. input:focus) straight from JS code?
No JQuery please :-) Just straight-up JS.
Thanks in advance!
You would need to add an event listener to the element in order to change the style of it. Here is a very basic example.
var input = document.getElementById("something");
input.addEventListener("focus", function () {
this.style.backgroundColor = "red";
});
<input type="text" id="something" />
Other alternative would be to build a stylesheet for the page.
Something like this:
var styles='input:focus {background-color:red}';
var styleTag=document.createElement('style');
if (styleTag.styleSheet)
styleTag.styleSheet.cssText=styles;
else
styleTag.appendChild(document.createTextNode(styles));
document.getElementsByTagName('head')[0].appendChild(styleTag);
This way you will have clean separation of css styles from the scripts and so the better maintenance.
Use CSS Variables if possible
It's 2022 and there are more simple solutions to this problem than adding event listeners all over the place that may never get cleaned up.
Instead if you have control over the CSS simply do this in your CSS:
.my-class {
--focusHeight: 32px;
--focusWidth: 256px;
}
.my-class:focus {
height: var(--focusHeight);
width: var(--focusWidth);
}
Then in your JavaScript it's as simple as using setProperty to update the variables:
const el = document.getElementById('elementId');
el.style.setProperty('--focusHeight', newFocusHeight);
el.style.setProperty('--focusWidth', newFocusWidth);
At first, create your input:
<input type="text" id="myElementID" />
Then add the javascript the following javascript:
const element = document.getElementById("myElementID");
// Add a box shadow on focus
element.addEventListener("focus", (e) => {
e.target.style.boxShadow = "0 0 0 3px #006bff40";
});
// Remove the box shadow when the user doesn't focus anymore
element.addEventListener("blur", (e) => {
e.target.style.boxShadow = "";
});
A quick oneliner, which dynamically appends a style tag to the
body.
document.body.innerHTML += '<style>#foo:focus {background-color:gold}</style>'
<input id="foo"/>
let input = document.querySelector(".input-text");
let label = document.querySelector('.fields-label');
input.addEventListener('focus', function(e){
label.classList.add('is-active');
})
input.addEventListener('blur', function(e){
if(input.value === "") {
label.classList.remove('is-active');
}
})
label.is-active{
color:red;
}
<div class="input-fields">
<label class="fields-label">Last Name</label>
<input type="text" class="input-text">
</div>

Categories

Resources