How do you get the style of focussed HTML element using JavaScript? - javascript

I would like to get the computed style of an element when it is focussed. I am using puppeteer and I would like to compare the CSS before focus to the CSS after focus. I can successfully get focus on an element using element.focus() and can confirm it's focussed by checking the active element (document.activeElement). However, the computed style (getComputedStyle()) for this element is the same as before focus, which is not what I expected.
Is there a way to get the CSS of the element after focus?
To be clear, I am writing a JavaScript tool which can scrape any website to check if the outline or border of an element under focus has sufficient contrast with the background on the page (such that it conforms with accessibility guidelines https://www.w3.org/WAI/WCAG21/Understanding/non-text-contrast.html). To do this I need to be able to get the colour of the outline/border when the element is under focus. Normally a focus outline would be defined in the CSS under :focus { ... } so this is what I wish to retrieve for any element.

you need to pay attention to the order of code in the script. if you change the style before the getComputedStyle() you'll get the change.
exmple:
document.querySelector("input").addEventListener("focus", (e) => {
console.log(window.getComputedStyle(e.target, null).color);
e.target.style.color = "red";
console.log(window.getComputedStyle(e.target, null).color);
e.target.style.color = "blue";
})
<input type="text" style="color: blue;">

As getComputedStyle returns a live CSSStyleDeclaration, you have to record the CSS styles by yourself manually to keep the CSS before focus.
Here's a working example:
let allInputs = document.querySelectorAll('input')
let beforeFocusStyles = Array.from(allInputs).reduce(function(final, elem) {
final[elem.dataset.identifier] = (function() {
let liveStyle = window.getComputedStyle(elem)
let value = {}
for (let key in liveStyle) {
value[key] = liveStyle[key]
}
return value
})()
return final
}, {})
Array.from(allInputs).forEach(elem => {
elem.onfocus = function() {
let afterFocusStyle = window.getComputedStyle(elem)
let differenceInStyle = (function() {
let beforeFocusStyle = beforeFocusStyles[elem.dataset.identifier]
let differences = []
for (let key in afterFocusStyle) {
if (beforeFocusStyle[key] !== afterFocusStyle[key]) {
differences.push([key, beforeFocusStyle[key], afterFocusStyle[key]])
}
}
return differences
})()
differenceInStyle.forEach(difference => {
console.log(difference[0], difference[1], difference[2])
})
}
})
.type1 {
outline: 1px solid black;
border: none;
}
.type1:focus {
outline: 2px solid red;
border: none;
}
.type2 {
outline: 1px solid blue;
border: none;
}
.type2:focus {
outline: 2px solid green;
border: none;
}
<input type="text" class="type1" data-identifier="1">
<input type="text" class="type2" data-identifier="2">

Related

How to make this div scrollTop value match that of the textarea?

I'm using a div to format and display the text from a textarea of equal dimensions and I need them to be permanently in sync. However, I haven't been able to synchronize their respective scrollTops after the input text goes past the bottom of the textarea.
My process has been similar to the one described here, however I can't get his solution to work on my project.
Here's a demo and snippets of the minimum relevant code:
<section>
<div class="input-text__container">
<div id="input-text--mirror" class="input-text"></div>
<textarea
id="input-text--original"
cols="30"
rows="6"
autofocus
class="input-text"
placeholder="Enter your text here"
autocomplete="off"
autocorrect="off"
spellcheck="false"
></textarea>
</div>
<section>
#import url('https://fonts.googleapis.com/css2?family=Inter:wght#400;500&display=swap');
html {
font-size: 62.5%;
box-sizing: border-box;
scroll-behavior: smooth;
}
*,
*::after,
*::before {
margin: 0;
padding: 0;
border: 0;
box-sizing: inherit;
vertical-align: baseline;
}
body {
height: 100vh;
}
section {
display: flex;
flex-direction: column;
min-height: 100%;
padding: 1rem;
}
.input-text__container {
width: 100%;
position: relative;
flex: 1;
}
.input-text {
width: 100%;
height: 100%;
position: absolute;
font-size: 3.2rem;
overflow-wrap: break-word;
font-family: "Inter";
}
#input-text--mirror {
background-color: #e9ecf8;
color: #0a3871;
overflow: hidden;
}
#input-text--original {
background-color: transparent;
-webkit-text-fill-color: transparent;
resize: none;
outline: none;
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
#input-text--original::placeholder {
color: #e9ecf8;
-webkit-text-fill-color: #052051;
}
#input-text--original::selection {
-webkit-text-fill-color: #ffffff;
}
.invalid {
font-weight: 400;
color: #ff0000;
}
#input-text--original::-webkit-scrollbar {
display: none;
}
let invalidInput = false;
const patterns = {
valid: "a-z ",
invalid: "[^a-z ]",
linebreaks: "\r|\r\n|\n",
};
const textIn = document.querySelector("#input-text--original");
const mirror = document.querySelector("#input-text--mirror");
function validateInput(string, className) {
let anyInvalidChar = false;
// Generate regular expressions for validation
const regExpInvalids = new RegExp(patterns.invalid, "g");
const regExpLinebreaks = new RegExp(patterns.linebreaks);
// Generate innerHTML for mirror
const mirrorContent = string.replace(regExpInvalids, (match) => {
if (regExpLinebreaks.test(match)) {
return "<br/>";
} else {
anyInvalidChar = true;
return `<span class=${className}>${match}</span>`;
}
});
// Update mirror
mirror.innerHTML = mirrorContent;
return anyInvalidChar;
}
textIn.addEventListener("input", (e) => {
const plain = textIn.value;
const newInputValidity = validateInput(plain, "invalid");
mirror.scrollTop = textIn.scrollTop;
});
textIn.addEventListener(
"scroll",
() => {
mirror.scrollTop = textIn.scrollTop;
},
{ passive: true }
);
On a desktop screen typing the first 8 natural numbers in a column should be enough to reproduce the issue.
The last thing I checked, but perhaps the most relevant so far was this. It seems to deal with the exact same issue on React, but I'm afraid I don't know how to adapt that solution to Vanilla JavaScript, since I'm just starting to learn React. Please, notice, I'm trying to find a solution that doesn't depend on libraries like jQuery or React.
Besides that, I tried the solution described in the aforementioned blog, by replacing return "<br/>"; with return "<br/> "; in my validateInput function but that didn't work. I also added a conditional to append a space to plain in const plain = textIn.value; in case the last char was a linebreak, but I had no luck.
I also included console.log commands before and after mirror.scrollTop = textIn.scrollTop; in the textIn scroll handler to track the values of each scrollTop and even when they were different, the mirror scrollTop wasn't updated. I read it might be because divs weren't scrollable by default, but adding "overflow: scroll" to its styles didn't fix the problem either.
I read about other properties related to scrollTop, like offsetTop and pageYOffset, but they're either read-only or not defined for divs.
I've reviewed the following posts/sites, too, but I've still haven't been able to fix this problem.
https://codepen.io/Goweb/pen/rgrjWx
https://stackoverflow.com/questions/68092068/making-two-textareas-horizontally-scroll-in-sync
Scrolling 2 different elements in same time
React : setting scrollTop property of div doesn't work
sync scrolling of textarea input and highlighting container
.scrollTop(0) not working for getting a div to scroll to the top
How to attach a scroll event to a text input?
I no longer remember what else I've reviewed, but nothing has worked and I no longer know what else to do. Thank you for your attention and help.
After trying to replicate the solution for a React app that I mentioned in the post, using vanilla JavaScript (demo here), I tried to apply that to my own project and all I had to do was adding a <br> tag to the mirror in the end of my validateInput function. That is: mirror.innerHTML = mirrorContent + "<br>";.
Besides that, updating the mirror's scrollTop every time the input event on the textarea was triggered was not needed. Neither was it to pass the { passive: true } argument to the scroll event.
The modified code is here:
function validateInput(string, className) {
let anyInvalidChar = false;
// Generate regular expressions for validation
const regExpInvalids = new RegExp(patterns.invalid, "g");
const regExpLinebreaks = new RegExp(patterns.linebreaks);
// Generate innerHTML for mirror
const mirrorContent = string.replace(regExpInvalids, (match) => {
if (regExpLinebreaks.test(match)) {
return "<br/>";
} else {
anyInvalidChar = true;
return `<span class=${className}>${match}</span>`;
}
});
// Update mirror
mirror.innerHTML = mirrorContent + "<br>";
return anyInvalidChar;
}
textIn.addEventListener("input", (e) => {
const plain = textIn.value;
const newInputValidity = validateInput(plain, "invalid");
});
textIn.addEventListener("scroll", () => mirror.scrollTop = textIn.scrollTop);

JS Variable Cannot Change Element Properties

Quick question here, I encountered this problem today while practicing some JS. I wanted to create a basic prototype to loop through a "div" background-color array on click, but I realized that assigning the element property to a variable (instead of using the event target) impedes me to change the actual values.
This is the JS code:
let colors = ["blue", "yellow", "orange", "red"]
let n = 1;
document.querySelectorAll('div').forEach(occurence => {
occurence.addEventListener('click', (e) => {
let classes = e.target.className;
classes = colors[n];
n++;
console.log(classes);
if (n >= 4) {n = 0;}
});
});
So, changing the actual e.target.className works just fine, but trying to change the assigned "classes" variable does nothing. I feel like this may be a matter of specificity, or JS being unable to access the actual property values, or some akin beginner mistake.
e.target.className passes by value when you have let classes = e.target.className, so classes contains a copy of its data. Changing classes just changes the copy, rather than what's stored in e.target.classname.
Actually, you are not changing the value of e.target.className. What you do, is assigning the value of e.target.className to the variable/let-binding classes. To assign one of the color values to the className property, the assignment has to be the other way around:
e.target.className = colors[n];
let classes = e.target.className will assign the current string value of className to classes. And while you can assign a new colour value to classes that won't assign the new colour value to the className property of the element. For that you want to explicitly assign it: e.target.className = colors[i].
You may also want to remove the need to add a event listener to all the elements. Event delegation allows you to add one listener to a parent element which captures events from its child elements as they "bubble up" the DOM.
Here's an over-wrought example:
const colors = ['blue', 'yellow', 'orange', 'red'];
// Cache the elements first, and add a listener to
// the container
const counter = document.querySelector('.index');
const container = document.querySelector('.container');
container.addEventListener('click', handleClick);
let count = 0;
function handleClick(e) {
// Check to see if the element that was clicked
// was a div element
if (e.target.matches('.container div')) {
// Update the counter element, the `className` value,
// and the `textContent of the "box", and then update
// the count value
counter.textContent = `Color index: ${count}`;
e.target.className = colors[count];
e.target.textContent = colors[count];
count = count < colors.length - 1 ? ++count : 0;
}
}
.container { display: grid; gap: 0.4em; grid-template-columns: repeat(2, 50px); }
.container div { display: flex; justify-content: center; align-items: center; height: 50px; width: 50px; border: 1px solid lightgray; }
.container div:hover { cursor: pointer; border: 1px solid darkgray; }
.blue { background-color: lightblue; }
.yellow { background-color: #ffff00; }
.orange { background-color: darkorange; }
.red { background-color: red; }
.index { margin-top: 0.5em; }
<div class="container">
<div></div>
<div></div>
<div></div>
<div></div>
</div>
<div class="index">Color index:</div>

Toggle add and remove class in JavaScript with if condition

I have a element with an id="caretToggle" and a button with onclick="caretToggle()". This fires a function that adds a class to invert the caret on the button.
I am successful in running:
function caretToggle() {
var caretElement = document.getElementById("caretToggle");
caretElement.classList.add("dropup");
}
But This leaves the caret inverted after the collapse is closed. I want to toggle the caret once the button is clicked again.
This is my condition code that I have failed to get working:
function caretToggle() {
var caretElement = document.getElementById("caretToggle");
if (caretElement.classList.contains("dropup")) {
caretElement.classList.remove("dropup");
} else {
caretElement.classList.add("dropup");
}
}
Thank you in advance for any help you may provide!
You dont need to check wheter contains or not.
What you can do simply use toggle function on classList :)
function caretToggle() {
var caretElement = document.getElementById("caretToggle");
caretElement.classList.toggle("dropup");
}
And also there is a conditional toggle like:
caretElement.classList.toggle("dropup", counter < 10)
Check here from MDN
If you want to toggle class simply do it like this
let caretElement = document.getElementById("caretToggle");
function caretToggle() {
caretElement.classList.toggle("dropup");
console.log('class attribute contains: ', caretElement.className)
}
span {
margin:10px;
}
.dropup {
background-color: purple;
padding: 1em;
border-radius: 10px;
color: white;
}
<span id="caretToggle">HTMLElement</span>
<br/>
<br/>
<br/>
<br/>
<button onclick="caretToggle()">Click</button>

How to change style of a button in javascript? [duplicate]

This question already has answers here:
How can I change an element's class with JavaScript?
(33 answers)
Closed 2 years ago.
I have a button whose original style is display: none, and after activating a function i want to change his style to an appear using style of defined class style.
I have the pause button as follows:
<li><button className="pause_button"
onClick={() =>{this.props.pause_resume();}}>
</button>
</li>
and the styles
.sortButton {
background-color: ivory;
color: black;
padding: 10px 25px;
border-radius: 3px;
margin: 5px;
font-size: 16px;
opacity: 1;
}
.sortButton:hover {
background-color: dodgerblue;
color: white;
opacity: 1;
}
.pause_button {
display: none;
}
I am trying to change the style of pause_button to be the style of sortButton
Here is what i tried, but unfortunately no change appears.
const pause_button = document.getElementsByClassName("pause_button");
pause_button.style = "sortButton";
In a different part of my code I'm using the same way and it does make changes to the button.
const buttons = Array.from(document.getElementsByClassName("sortButton"));
buttons.forEach(button => {
button.style = "sortButton";
})
The last bit of code happens after I have changed the buttons style in this way.
const buttons = Array.from(document.getElementsByClassName("sortButton"));
buttons.forEach(button => {
if(button.innerText !== text){
button.style.backgroundColor = 'grey';
button.style.opacity= 0.2;
}
})
All the changes except from the pause button appearing works,
Two things: style is an object, not a string. You need to change the element's class instead. And getElementsByClassName returns a collection of elements. In your code:
const pause_button = document.getElementsByClassName("pause_button");
pause_button.style = "sortButton";
pause_button is an HTMLCollection. You can use Array.from() like you did in the other example, or you can iterate over it directly using for...of.
const buttons = getElementsByClassName("pause_button");
for (button of buttons) {
button.className = "sortButton";
}
You have to use Element.className on the DOM Node to associate a class to a particular node. To apply the class "sortButton" you have to do the following:
const buttons = Array.from(document.getElementsByClassName("sortButton"));
buttons.forEach(button => { button.className = "sortButton" });

Is it possible to loop through the style attributes of a div with javascript or jquery?

As the title says I am wondering if it is possible to loop through the style attributes of a div with javascript or jquery. What I want to do is loop through the styles and create an object containing these style names and values.
Here is an example of what I am trying to do:
alert($('#mydiv').attr('style'));
Gives the following:
background-color: #CCCCCC; border-width: 2px; border-style: solid; width: 250px;
And I want to create a object which looks like this:
{"background-color":"#CCCCCC","border-width":"2px","border-style":"solid","width":"250px"}
What I can't figure out is whether this is achievable by looping through the styles or whether I will have to create the object myself using code similar to below:
var style = {};
style['width'] = $('#mydiv').css('width');
Any input of this would be appreciated.
Version 1, using inline style
const style = $("#myDiv").attr("style");
const parts = style.split(";")
console.log(parts)
let obj = {}
parts.forEach(part => {
if (part.length > 0) { // skip the empty element after the last ;
const [key,val] = part.split(':');
obj[key] = val.trim();
}
})
console.log(obj)
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<div id="myDiv" style="background-color: #CCCCCC; border-width: 2px; border-style: solid; width: 250px;">My div</div>
Version 2 using computed style of a list of elements
// more code needed to handle rgba
const rgbToHex = rgb => '#' + (rgb.match(/[0-9|.]+/g).map((x, i) => i === 3 ? parseInt(255 * parseFloat(x)).toString(16) : parseInt(x).toString(16)).join('')).padStart(2, '0').toUpperCase();
let obj = {}
const style = window.getComputedStyle(document.getElementById('myDiv')); //
["background-color", "border-width", "border-style", "width"]
.forEach(rule => {
const val = style.getPropertyValue(rule)
obj[rule] = val.includes('rgb') ? rgbToHex(val) : val;
})
console.log(obj)
#myDiv {
background-color: #CCCCCC;
border-width: 2px;
border-style: solid;
width: 250px;
}
<div id="myDiv">My div</div>
For <div id="id" style="color: red"/>, $('#id').attr('style') will return a string color: red so i guess, you can't loop them directly.
However you can create an array string.split(';') and loop over them.
But CSS associated with in inside <style> tag or some css file, i don't think you can get it. However, I am not sure.

Categories