How do I change the background dynamically? - javascript

I am creating a weather App and I want to change the background dynamically according to the fetched data from the API.
I have created a variable and an if statement but I was having trouble assigning the variable because I was using module.scss and I'm not sure of the syntax.
render() {
let bgColorClass = 'App'; // very-warm, warm, normal, cold, very-cold
if (this.state.main === "rain") {
bgColorClass = 'rain';
}
else if (this.state.main === "thunderstorm") {
bgColorClass = 'thunder';
}
else if (this.state.main === "drizzle") {
bgColorClass = 'drizzle';
}
else if (this.state.main === "Snow") {
bgColorClass = 'snow';
}
else if (this.state.main === "Clear") {
bgColorClass = 'sun';
}
else if (this.state.main === "Clouds") {
bgColorClass = 'clouds';
} else {
bgColorClass = 'else'
}
return (
<div className={Styles.App} style={{bgColorClass}}>
<Header />
<Form
getWeather={this.getWeather} />
<Weather
temperature={this.state.temperature}
city={this.state.city}
country={this.state.country}
description={this.state.description}
main={this.state.main}
Right now it's not working. I have defined the css files as this:
$sun: #ffc600;
$rain: #94AF10;
$drizzle: #06799F;
$thunder: #233884;
$snow: #707ba5;
$clouds: #686b77;
$else: #842343;
.App {
text-align: center;
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: center;
background-color: $drizzle;
//background: linear-gradient(0deg, rgb(66, 61, 160) 0%, rgba(0,212,255,1) 100%);
color: white;
height: 100vh;
&.sun{ background-color: $sun; }
&.rain { background-color: $rain; }
&.drizzle { background-color: $drizzle; }
&.thunder { background-color: $thunder; }
&.snow { background-color: $snow; }
&.clouds { background-color: $clouds; }
&.else { background-color: $else; }
}

Right now you are echoing a single word into the inline style attribute, which isn't valid CSS. You will need to add the bgColorClass to the className attribute of your div.
BTW you're using tons of if statements. You also can use a switch statement
switch (this.state.main) {
case "rain":
bgColorClass = "rain";
break;
case "drizzle":
bgColorClass = "drizzle";
break;
// and so on…
default:
bgColorClass = "else";
break;
}
or even better: Rename all your CSS classes to the possible state values and directly insert the value as the class:
<div className={"App " + this.state.main}>
// e.g. if this.state.main is 'rain', then the output would be
<div class="App rain">
With this trick you can omit the conditional if or switch statements

add bgColorClass in your className
example:
<div className={"App "+ bgColorClass}>

Related

Modify shadow root on css

I'm trying with a thousand different css rules, but I can't find any that do what I require.
I have a desire to edit a custom element in my css file after it is placed in the dom, but I can't, how can I do?
If there is already a question asked, I apologize and please link it to me
class GacInput extends HTMLElement {
constructor() {
super();
this.root = this.attachShadow({
mode: 'open'
});
this.root.innerHTML = `
<style>
:host {
display: flex;
width: min-content;
}
input {
display: block;
}
</style>
<label></label>
<input type="text">
`;
}
static get observedAttributes() {
return ['label', 'align'];
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'label') {
this.root.querySelector('label').innerText = newValue;
}
if (name === 'align') {
switch (newValue) {
case "right":
this.root.host.style.flexDirection = "row-reverse";
break;
case "top":
this.root.host.style.flexDirection = "column";
break;
case "bottom":
this.root.host.style.flexDirection = "column-reverse";
break;
default:
this.root.host.style.flexDirection = "row";
}
}
}
}
customElements.define('gac-input', GacInput);
gac-input::shadow label {
background-color: blue;
}
gac-input /deep/ label {
background-color: blue;
}
:host(gac-input) label {
background-color: blue;
}
<gac-input label="nome" align="top"></gac-input>
Not sure if this will help with your specific case,
But my go-to for editing a css-property after the DOM is loaded is using variables.
For example:
:root {
--custom-color: #000000;
--flex-direction: column;
}
.element {
background-color: var(--custom-color);
flex-direction: var(--flex-direction);
}
And to change it via javascript:
const root = document.querySelector(':root');
root.style.setAttribute('--custom-color', '#999999');

Where should i put setTimeout()?

Codepen
let rock = document.querySelector(".rock-btn");
let paper = document.querySelector(".paper-btn");
let scissors = document.querySelector(".scissors-btn");
let select = document.querySelector(".select");
let computerChoice = document.querySelector(".comp-choice");
let result = document.querySelector(".result");
rock.addEventListener("click", function press() {
select.innerHTML = `Select: Rock`;
let randomNum = Math.floor(Math.random() * 3);
let compChoice = "";
if (randomNum == 0) {
compChoice = "Rock";
} else if (randomNum == 1) {
compChoice = "Paper";
} else if (randomNum == 2) {
compChoice = "Scissors";
}
result.style.removeProperty("color");
computerChoice.innerHTML = `Computer choose: ${compChoice}`;
if (compChoice == "Rock") {
result.innerHTML = "Result: It's a draw!";
result.style.color = "#5c3000";
} else if (compChoice == "Paper") {
result.innerHTML = "Result: Oh no, You lost!";
result.style.color = "#8d1d1d";
} else if (compChoice == "Scissors") {
result.innerHTML = "Result: Congratulations, You won!";
result.style.color = "#1e612b";
}
});
I want to click a rock,paper,scissors and then wait 1 seconds.Basically i want it delay. When i put it normal way it wont work after first time. I tried to solve on my on but sadly couldn't. I included codepen so that its easier for you to solve hopefully. Thanks for your help, I appreciate it.
❌ don't add a setTimeout of 1s,
✅ but instead, add some nice effect with CSS animations of 1s.
and for solving the bug
bug: first-time animation work, other times not start animation
you can use a setTimeout of also 1ms
1ms is enough for javascript to delete the class, so after we successfully deleted that class, now we can re-add it so we have an animation
Javascript that I added:
/* THIS IS THE TRICK */
setTimeout(() => {
result.classList.add("animation")
}, 1); // 1ms of Timeout
CSS that I added:
/* animation, this is added by javascript */
.animation {
animation: move 1s ease-in-out;
}
#keyframes move {
0% {
transform: translateX(100%);
}
100% {
transform: translateX(0);
}
}
don't make the user wait for no reason,
instead make them see some beautiful animation that makes the user think that the site is faster.
with setTimeout the user thinks that the site is a lot slower.
I also refactored it for you, so you don't need to write multiple time the same code.
using forEach() and JSON object and arrays and switch statement.
/* the buttons */
let rock = document.querySelector(".rock-btn");
let paper = document.querySelector(".paper-btn");
let scissors = document.querySelector(".scissors-btn");
/* elements where we will put a output the outputs */
let select = document.querySelector(".select");
let computerChoice = document.querySelector(".comp-choice");
let result = document.querySelector(".result");
/* all the buttons are one array,
so we can use them in a forEach loop,
without repeating a lot the code */
let allButtons = [rock, paper, scissors];
/* this is a array of choices (strings)
it must need to not be modified the order
1. Rock 2. Paper 3. Scrissors */
let allChoices = ["Rock", "Paper", "Scissors"];
/* the firt {} is for victory,
the second is for draw,
the third is for losing */
/* and this array contain all the information you need
for outputing the victory string with the color in #result */
let allResults = [{
message: "Result: Congratulations, You won!",
color: "#1e612b"
}, {
message: "Result: It's a draw!",
color: "#5c3000"
}, {
message: "Result: Oh no, You lost!",
color: "#8d1d1d"
}];
/* from this array we can see if user win or lose,
it must need to not be modified the order
1. Rock 2. Paper 3. Scrissors */
let winLoseArray = [{
toWin: allChoices[2],
toLose: allChoices[1]
}, {
toWin: allChoices[0],
toLose: allChoices[2]
}, {
toWin: allChoices[1],
toLose: allChoices[0]
}]
/* forEach loop help us not writing the same code multiple times */
allButtons.forEach((choice, index) => {
choice.addEventListener("click", () => {
/* resetting the styles and animations */
result.classList.remove("animation");
result.style.removeProperty("color");
/* random number */
let randomNum = Math.floor(Math.random() * 3);
/* get the choices of user, and computer */
let compChoice = allChoices[randomNum];
let userChoice = allChoices[index];
/* display the choices in the UI */
select.innerHTML = `select: ${userChoice}`;
computerChoice.innerHTML = `Computer choose: ${compChoice}`;
/* instead of If Else, We Will be using "switch" */
switch (compChoice) {
case winLoseArray[index].toWin:
result.innerHTML = allResults[0].message;
result.style.color = allResults[0].color;
break;
case winLoseArray[index].toLose:
result.innerHTML = allResults[2].message;
result.style.color = allResults[2].color;
break;
default:
result.innerHTML = allResults[1].message;
result.style.color = allResults[1].color;
break;
}
/* THIS IS THE TRICK */
setTimeout(() => {
result.classList.add("animation")
}, 1);
});
});
.container {
margin-top: 100px;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
gap: 25px;
}
h1 {
font-size: 45px;
}
p {
font-size: 35px;
margin: 0;
}
.btn {
padding: 8px 16px;
font-size: 20px;
background-color: #c2255c;
color: #fff;
border-radius: 20px;
margin-right: 5px;
}
.rock-btn:hover {
background-color: #70bdc9;
}
.paper-btn:hover {
background-color: #f8e9d1;
}
.scissors-btn:hover {
background-color: #89d0b4;
}
.rock-btn {
background-color: #4cacbc;
}
.paper-btn {
background-color: #f6e3c5;
}
.scissors-btn {
background-color: #6cc4a1;
}
img {
width: 100px;
border-radius: 10px;
}
.icons {
display: flex;
justify-content: space-between;
gap: 15px;
}
body {
background-color: rgb(136, 152, 167);
}
/* animation, this is added by javascript */
.animation {
animation: move 1s ease-in-out;
}
#keyframes move {
0% {
transform: translateX(100%);
}
100% {
transform: translateX(0);
}
}
<div class="container">
<h1>Rock Paper Scissors!</h1>
<p class="select">Select:</p>
<div class="icons">
<img alt="" src="https://www.shareicon.net/data/256x256/2015/11/15/672596_open_512x512.png" class="scissors-btn">
<img src="https://i.pinimg.com/originals/f5/2a/23/f52a2361f819785f37256d8eacd64a0d.png" class="paper-btn">
<img src="https://img.icons8.com/emoji/256/rock-emoji.png" class="rock-btn">
</div>
<p class="comp-choice">Computer choice:</p>
<p class="result">Result:</p>
</div>
<script src="script.js"></script>

How to change all :root variables with one function

Similar question : But can't able to get answer
Can be answer to this question : But has to split on each :
If possible to get all variable in one function and change each values
If there are 2-4 variable than easy to change but when that change to 10 or more
var root = document.querySelector(':root');
function func() {
root.style.setProperty('--r', 'brown');
root.style.setProperty('--b', 'lightblue');
root.style.setProperty('--g', 'lightgreen');
}
:root {
--r: red;
--b: blue;
--g: green;
}
.text1 {
color: var(--r)
}
.text2 {
color: var(--b)
}
.text3 {
color: var(--g)
}
<div class="text1">Hello</div>
<div class="text2">Bye</div>
<div class="text3">World</div>
<button onclick="func()">Change</button>
Is there a way to automatic(dynamically) get all the variables without writing each variable while values array is self entered
var root = document.querySelector(':root');
var roots = getComputedStyle(root);
var re = roots.getPropertyValue('--r')
var bl = roots.getPropertyValue('--b')
var gr = roots.getPropertyValue('--g')
function func() {
root.style.setProperty('--r', 'brown');
root.style.setProperty('--b', 'lightblue');
root.style.setProperty('--g', 'lightgreen');
}
function func2() {
root.style.setProperty('--r', re);
root.style.setProperty('--b', bl);
root.style.setProperty('--g', gr);
}
:root {
--r: red;
--b: blue;
--g: green;
}
.text1 {
color: var(--r)
}
.text2 {
color: var(--b)
}
.text3 {
color: var(--g)
}
<div class="text1">Hello</div>
<div class="text2">Bye</div>
<div class="text3">World</div>
<button onclick="func()">Change</button>
<button onclick="func2()">Orignal</button>
But when getting back to original values then each value is entered by separate variable and for each it is defined .
Is there a way to have a approach which takes original values and variables in separate array automatically.
Thanks for help in advance
I understand you want to first read all the declared variables from the css and store them, so they might be resetted after applying new values?
This code will do this, given that there's just one ':root' declaration in one stylesheet (easily complemented in case 'splitted' declaration in more places needed)
let variableLookup = {
'--r': {
newValue: 'teal'
},
'--b': {
newValue: 'orange'
},
'--g': {
newValue: 'purple'
}
}
var root = document.querySelector(':root');
function setNewColors() {
const cssText = [...document.styleSheets]
.map(styleSheet => [...styleSheet.cssRules]
.filter(CSSStyleRule => CSSStyleRule.selectorText === ':root'))
.flat()[0].cssText
// cssText = ':root { --r: red; --b: blue; --g: green; }'
cssText.match(/{(.*)}/)[1].trim()
.split(';')
.filter(Boolean)
.forEach(declaration => {
const [variable, oldValue] = declaration.split(':').map(str => str.trim())
let entry = variableLookup[variable]
if (entry) entry.oldValue = oldValue
})
console.log('variableLookup >>', variableLookup)
Object.entries(variableLookup).forEach(([variable, {newValue}]) => {
root.style.setProperty(variable, newValue);
})
}
function resetColors() {
Object.entries(variableLookup).forEach(([variable, {oldValue}]) => {
if (oldValue) root.style.setProperty(variable, oldValue)
})
}
:root {
--r: red;
--b: blue;
--g: green;
}
.text1 {
color: var(--r)
}
.text2 {
color: var(--b)
}
.text3 {
color: var(--g)
}
:root {
--c: magenta;
}
<div class="text1">Hello</div>
<div class="text2">Bye</div>
<div class="text3">World</div>
<button onclick="setNewColors()">Change to new colors</button>
<button onclick="resetColors()">Reset old colors</button>
Since the OP is interested in a version without using .split() these could be replaced by using a regex and .match()
const declarations = '--r: red; --b: blue; --g: green;'
const regex1 = /^[\w-:\s]+(?=;)|(?<=;)[\w-:\s]+/g
const declarationsArr = declarations.match(regex1)
console.log(declarationsArr) // ["--r: red", " --b: blue", " --g: green"]
const regex2 = /[\w-]+(?=:)|(?<=:\s)[\w-]+/g
const declarationsSplit = declarationsArr.map(d => d.match(regex2))
console.log(declarationsSplit) // [["--r", "red"], ["--b", "blue"], ["--g", "green"]]
One method can be entering all variables and values in different arrays and fetching values from them . Where variables = values which need to be equal have same index number
var root = document.querySelector(':root');
variable = ['--r', '--b', '--g'];
values = ['violet', 'lightblue', 'lightgreen']
function func() {
for (let i = 0; i < 3; i++)
root.style.setProperty(variable[i], values[i]);
}
:root {
--r: red;
--b: blue;
--g: green;
}
.text1 {
color: var(--r)
}
.text2 {
color: var(--b)
}
.text3 {
color: var(--g)
}
<div class="text1">Hello</div>
<div class="text2">Bye</div>
<div class="text3">World</div>
<button onclick="func()">Change</button>
const root = document.documentElement;
function changeVariables(...values) {
const variables = ["--r", "--g", "--b"];
for (const variable of variables) {
root.style.setProperty(variable, values[variable.indexof(variables)]);
}
}
//Simply execute function like this
changeVariables("#000", "#808080", "#FF0000");
setting new-colors you want to change
backup original-colors
Try the example bellow
var root = document.querySelector(':root');
var $divEle = $("div[class^='text']"); // get all element with class starting with 'text'
var arrCssVar = ['--r', '--b','--g'];
var backupOrgColor= {}
var newColor = {'--r':'brown', '--b':'lightblue', '--g':'lightgreen'}; // ordering color what you want to change
// backup original colors
$divEle.map(function(i, v) {
if(i < arrCssVar.length){
var compStyle = getComputedStyle(this);
// setting key/value pair to Obj
backupOrgColor[arrCssVar[i]] = compStyle.getPropertyValue(arrCssVar[i]);
}
});
// change color
function setNewColors() {
arrCssVar.map(function (key, value) {
//console.log(key + ": key :change: value : "+ newColor[key]);
root.style.setProperty(key, newColor[key]);
});
}
// reset original color
function resetColors() {
arrCssVar.map(function (key, value) {
//console.log(key + ": key :: value : "+ backupOrgColor[key]);
root.style.setProperty(key, backupOrgColor[key]);
});
}
:root {
--r: red;
--b: blue;
--g: green;
}
.text1 {
color: var(--r)
}
.text2 {
color: var(--b)
}
.text3 {
color: var(--g)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="text1">Hello</div>
<div class="text2">Bye</div>
<div class="text3">World</div>
<button onclick="setNewColors()">Change</button>
<button onclick="resetColors()">Orignal</button>
Try this example.
Initialize two arrays, one is empty for the current colors form the css and second for a new colors.
Need to define how many variables, then get them and put in an empty array.
Create two functions with loops, in the first we assign the new colors for variables, in the second reset.
const root = document.body;
// Colors arrays
const cssVarColors = [];
const newColors = ['brown', 'lightblue', 'lightgreen'];
// Create an array of variable colors from css.
const cssRootArray = document.styleSheets[0].cssRules;
for (const i of cssRootArray) {
// Check, if :root in the css.
if (i.selectorText.includes('root')) {
const rootArrayLength = i.style.length;
for (let j = 0; j < rootArrayLength; j++) {
const key = i.style[j];
// Create object { key/variable : value/color }
const value = getComputedStyle(root).getPropertyValue(key);
cssVarColors.push({[key]: value});
}
}
}
// We change colors, with variable keys and indexes.
function changeColor() {
for (const i in cssVarColors) {
const key = Object.keys(cssVarColors[i]);
// Check, if newColor array don't have a color.
if (!newColors[i]) {
return;
}
root.style.setProperty(key, newColors[i]);
}
variables();
}
change.addEventListener('click', changeColor);
const root = document.body;
// Colors arrays
const cssVarColors = [];
const newColors = ['brown', 'lightblue', 'lightgreen'];
// Create an array of colors from a css variables file.
const cssRootArray = document.styleSheets[0].cssRules;
for (const i of cssRootArray) {
// Check, if :root in the css.
if (i.selectorText.includes('root')) {
const rootArrayLength = i.style.length;
for (let j = 0; j < rootArrayLength; j++) {
const key = i.style[j];
// Create object { key/variable : value/color }
const value = getComputedStyle(root).getPropertyValue(key);
cssVarColors.push({
[key]: value,
});
}
}
}
// We change colors, with variable keys and indexes.
function changeColor() {
for (const i in cssVarColors) {
const key = Object.keys(cssVarColors[i]);
// Check, if newColor array don't have a color.
if (!newColors[i]) {
return;
}
root.style.setProperty(key, newColors[i]);
}
variables();
}
// Can't separate in loop by [key, value],
// because key has a specific value.
function resetColor() {
for (const i in cssVarColors) {
if (!newColors[i]) {
return;
}
const key = Object.keys(cssVarColors[i]);
const value = Object.values(cssVarColors[i]);
root.style.setProperty(key, value);
}
variables();
}
// Change button
change.addEventListener('click', changeColor);
// Reset button
reset.addEventListener('click', resetColor);
// extra for view
cssVarColors.forEach(clr => {
const el = document.createElement('span');
el.textContent = JSON.stringify(clr);
document.getElementById('variables').appendChild(el);
});
:root {
--r: red;
--b: blue;
--g: green;
--m: magenta;
--black: black;
}
.text1 {
color: var(--r);
}
.text2 {
color: var(--b);
}
.text3 {
color: var(--g);
}
/* Extra style */
body {
display: grid;
grid-template-columns: 50px 150px;
grid-template-rows: auto;
}
section {
grid-column: 1;
}
#variables {
grid-column: 2;
display: flex;
flex-direction: column;
}
span:nth-of-type(1) {
border: 5px solid var(--r);
}
span:nth-of-type(2) {
border: 5px solid var(--b);
}
span:nth-of-type(3) {
border: 5px solid var(--g);
}
span:nth-of-type(4) {
border: 5px solid var(--m);
}
span:nth-of-type(5) {
border: 5px solid var(--black);
}
<section>
<div class="text1">Hello</div>
<div class="text2">Bye</div>
<div class="text3">World</div>
</section>
<div id="variables"></div>
<button id="change">Change</button>
<button id="reset">Reset</button>

Show images in quiz javascript

I'm trying to create a quiz that tests users awareness of real and fake emails. What I want to do is have the question displayed at the top saying "Real or Fake", then have an image displayed underneath which the user needs to look at to decided if it's real or fake. There are two buttons, real and fake, and regardless of whether they choose the right answer I want to swap the original image with annotated version - showing how users could spot that it was fake or real.
But I'm not sure how to show the annotated version once the answer has been submitted. Could someone help?
function Quiz(questions) {
this.score = 0;
this.questions = questions;
this.questionIndex = 0;
}
Quiz.prototype.getQuestionIndex = function() {
return this.questions[this.questionIndex];
}
Quiz.prototype.guess = function(answer) {
if (this.getQuestionIndex().isCorrectAnswer(answer)) {
this.score++;
}
this.questionIndex++;
}
Quiz.prototype.isEnded = function() {
return this.questionIndex === this.questions.length;
}
function Question(text, choices, answer) {
this.text = text;
this.choices = choices;
this.answer = answer;
}
Question.prototype.isCorrectAnswer = function(choice) {
return this.answer === choice;
}
function populate() {
if (quiz.isEnded()) {
showScores();
} else {
// show question
var element = document.getElementById("question");
element.innerHTML = quiz.getQuestionIndex().text;
// show options
var choices = quiz.getQuestionIndex().choices;
for (var i = 0; i < choices.length; i++) {
var element = document.getElementById("choice" + i);
element.innerHTML = choices[i];
guess("btn" + i, choices[i]);
}
showProgress();
}
};
function guess(id, guess) {
var button = document.getElementById(id);
button.onclick = function() {
quiz.guess(guess);
populate();
}
};
function showProgress() {
var currentQuestionNumber = quiz.questionIndex + 1;
var element = document.getElementById("progress");
element.innerHTML = "Question " + currentQuestionNumber + " of " + quiz.questions.length;
};
function showScores() {
var gameOverHTML = "<h1>Result</h1>";
gameOverHTML += "<h2 id='score'> Your scores: " + quiz.score + "</h2>";
var element = document.getElementById("quiz");
element.innerHTML = gameOverHTML;
};
// create questions here
var questions = [
new Question("<img src= 'netflix_fake.jpg' />", ["Real", "Fake"], "Fake"),
new Question("<img src= 'dropbox_real.jpg' />", ["Real", "Fake"], "Real"),
new Question("<img src= 'gov_real.jpg' />", ["Real", "Fake"], "Real"),
new Question("<img src= 'paypal_fake.jpg' />", ["Real", "Fake"], "Fake"),
new Question("<img src= 'gmail.jpg' />", ["Real", "Fake"], "Fake")
];
//create quiz
var quiz = new Quiz(questions);
// display
populate();
body {
background-color: #538a70;
}
.grid {
width: 600px;
height: 500px;
margin: 0 auto;
background-color: #fff;
padding: 10px 50px 50px 50px;
border: 2px solid #cbcbcb;
}
.grid h1 {
font-family: "sans-serif";
font-size: 60px;
text-align: center;
color: #000000;
padding: 2px 0px;
}
#score {
color: #000000;
text-align: center;
font-size: 30px;
}
.grid #question {
font-family: "monospace";
font-size: 30px;
color: #000000;
}
.buttons {
margin-top: 30px;
}
#btn0,
#btn1,
#btn2,
#btn3 {
background-color: #a0a0a0;
width: 250px;
font-size: 20px;
color: #fff;
border: 1px solid #1D3C6A;
margin: 10px 40px 10px 0px;
padding: 10px 10px;
}
#btn0:hover,
#btn1:hover,
#btn2:hover,
#btn3:hover {
cursor: pointer;
background-color: #00994d;
}
#btn0:focus,
#btn1:focus,
#btn2:focus,
#btn3:focus {
outline: 0;
}
#progress {
color: #2b2b2b;
font-size: 18px;
}
<div class="grid">
<div id="quiz">
<h1>Can you spot the fake email?</h1>
<hr style="margin-bottom: 20px">
<p id="question"></p>
<div class="buttons">
<button id="btn0"><span id="choice0"></span></button>
<button id="btn1"><span id="choice1"></span></button>
</div>
<hr style="margin-top: 50px">
<footer>
<p id="progress">Question x of y</p>
</footer>
</div>
</div>
When user clicks button I trigger class and I add it second name, on second I have written to get swapped, I wrote you basically full project, and please read the whole comments, to understand logic
//Calling Elements from DOM
const button = document.querySelectorAll(".check");
const images = document.querySelectorAll(".image");
const answer = document.querySelector("h1");
//Declaring variable to randomly insert any object there to insert source in DOM Image sources
let PreparedPhotos;
//Our Images Sources and With them are its fake or not
//fake: true - yes its fake
//fake: false - no its real
const image = [
[
{
src:
"https://upload.wikimedia.org/wikipedia/commons/thumb/e/ec/Mona_Lisa%2C_by_Leonardo_da_Vinci%2C_from_C2RMF_retouched.jpg/1200px-Mona_Lisa%2C_by_Leonardo_da_Vinci%2C_from_C2RMF_retouched.jpg",
fake: true
},
{
src:
"http://graphics8.nytimes.com/images/2012/04/13/world/europe/mona-lisa-like-new-images/mona-lisa-like-new-images-custom4-v3.jpg",
fake: false
}
],
[
{
src:
"https://cdn.shopify.com/s/files/1/0849/4704/files/Creacion_de_Adan__Miguel_Angel_f5adb235-bfa8-4caa-8ffb-c5328cbad953_grande.jpg?12799626327330268216",
fake: false
},
{
src:
"https://cdn.shopify.com/s/files/1/0849/4704/files/First-image_Fb-size_grande.jpg?10773543754915177139",
fake: true
}
]
];
//Genrating Random Photo on HTML
function setRandomPhoto() {
//Random Number which will be length of our array of Object
//if you array includes 20 object it will generate random number
// 0 - 19
const randomNumber = Math.floor(Math.random() * image.length);
//Decalaring our already set variable as Array Object
PreparedPhoto = image[randomNumber];
//Our first DOM Image is Variables first object source
images[0].src = PreparedPhoto[0].src;
//and next image is next object source
images[1].src = PreparedPhoto[1].src;
}
//when windows successfully loads, up function runs
window.addEventListener("load", () => {
setRandomPhoto();
});
//buttons click
//forEach is High Order method, basically this is for Loop but when you want to
//trigger click use forEach - (e) is single button whic will be clicked
button.forEach((e) => {
e.addEventListener("click", () => {
//decalring variable before using it
let filtered;
//finding from our DOM image source if in our long array exists
//same string or not as Image.src
//if it exists filtered variable get declared with that found obect
for (let i = 0; i < image.length; i++) {
for (let k = 0; k < 2; k++) {
if (image[i][k].src === images[0].src) {
filtered = image[i][k];
}
}
}
//basic if else statement, if clicked button is Fake and image is true
//it outputs You are correct
//if clicked button is Real and Image is false it outputs Correct
//Else its false
//Our image checking comes from filtered variable
if (e.innerText === "Fake" && filtered.fake === true) {
answer.innerText = "You Are Correct";
images.forEach((image) => {
image.classList.toggle("hidden");
});
} else if (e.innerText === "Real" && filtered.fake === false) {
answer.innerText = "You Are Correct";
images.forEach((image) => {
image.classList.toggle("hidden");
});
} else {
answer.innerHTML = "You are Wrong";
images.forEach((image) => {
image.classList.toggle("hidden");
});
}
});
});
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.container {
width: 100%;
min-height: 100vh;
display: flex;
justify-content: space-around;
align-items: center;
flex-direction: column;
}
.image-fluid {
display: flex;
}
.image-fluid .image {
width: 200px;
margin: 0 10px;
transition: 0.5s;
}
.image-fluid .image:nth-child(1).hidden {
transform: translateX(110px);
}
.image-fluid .image:nth-child(2).hidden {
transform: translateX(-110px);
}
<div class="container">
<div class="image-fluid">
<img src="" class="image hidden">
<img src="" class="image hidden">
</div>
<div class="button-fluid">
<button class="check">Fake</button>
<button class="check">Real</button>
</div>
</div>
<h1></h1>

How can I get the containing block of a "fixed" positioned element with javascript?

Let's say we have the following setup:
#header {
background-color: #ddd;
padding: 2rem;
}
#containing-block {
background-color: #eef;
padding: 2rem;
height: 70px;
transform: translate(0, 0);
}
#button {
position: fixed;
top: 50px;
}
<div id="header">header</div>
<div id="containing-block">
containing-block
<div>
<div>
<div>
<button id="button" onclick="console.log('offsetParent', this.offsetParent)">click me</button>
</div>
</div>
</div>
</div>
where the button has fixed position and the containing-block has a transform property in place.
This might come as a surprise, but the button is positioned relative to the #containing-block, not the viewport (as one would expect when using fixed). That's because the #containing-block element has the transform property set. See https://developer.mozilla.org/en-US/docs/Web/CSS/position#fixed for clarification.
Is there an easy way to find out which is the containing block of the button? Which is the element top: 50px is calculated in respect to? Assume you don't have a reference to the containing block and you don't know how many levels up it is. It may even be the documentElement if there are no ancestors with transform, perspective or filter properties set.
For absolute or relative positioned elements, we have elem.offsetParent which gives us this reference. However, it is set to null for fixed elements.
Of course, I could look up the dom and find the first element that has a style property of transform, perspective or filter set, but this seems hacky and not future proof.
Thanks!
Known behavior and spec compliant. spec should probably be changed though.
https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetParent
I've included a few workarounds from various libraries.
Workaround taken from dom-helpers (seems to be most consistent and use of offsetParent to traverse means it should only ever really traverse once or twice.):
https://github.com/react-bootstrap/dom-helpers/blob/master/src/offsetParent.ts
// taken from popper.js
function getStyleComputedProperty(element, property) {
if (element.nodeType !== 1) {
return [];
}
// NOTE: 1 DOM access here
const window = element.ownerDocument.defaultView;
const css = window.getComputedStyle(element, null);
return property ? css[property] : css;
}
getOffsetParent = function(node) {
const doc = (node && node.ownerDocument) || document
const isHTMLElement = e => !!e && 'offsetParent' in e
let parent = node && node.offsetParent
while (
isHTMLElement(parent) &&
parent.nodeName !== 'HTML' &&
getComputedStyle(parent, 'position') === 'static'
) {
parent = parent.offsetParent
}
return (parent || doc.documentElement)
}
#header {
background-color: #ddd;
padding: 2rem;
}
#containing-block {
background-color: #eef;
padding: 2rem;
height: 70px;
transform: translate(0, 0);
}
#button {
position: fixed;
top: 50px;
}
<div id="header">header</div>
<div id="containing-block">
containing-block
<div>
<div>
<div>
<button id="button" onclick="console.log('offsetParent', getOffsetParent(this),this.offsetParent)">click me</button>
</div>
</div>
</div>
</div>
Workaround code taken from jQuery source. Doesn't deal with non-element, nor TABLE TH TD, but it's jQuery.
https://github.com/jquery/jquery/blob/master/src/offset.js
// taken from popper.js
function getStyleComputedProperty(element, property) {
if (element.nodeType !== 1) {
return [];
}
// NOTE: 1 DOM access here
const window = element.ownerDocument.defaultView;
const css = window.getComputedStyle(element, null);
return property ? css[property] : css;
}
getOffsetParent = function(elem) {
var doc = elem.ownerDocument;
var offsetParent = elem.offsetParent || doc.documentElement;
while (offsetParent &&
(offsetParent !== doc.body || offsetParent !== doc.documentElement) &&
getComputedStyle(offsetParent, "position") === "static") {
offsetParent = offsetParent.parentNode;
}
return offsetParent;
}
#header {
background-color: #ddd;
padding: 2rem;
}
#containing-block {
background-color: #eef;
padding: 2rem;
height: 70px;
transform: translate(0, 0);
}
#button {
position: fixed;
top: 50px;
}
<div id="header">header</div>
<div id="containing-block">
containing-block
<div>
<div>
<div>
<button id="button" onclick="console.log('offsetParent', getOffsetParent(this),this.offsetParent)">click me</button>
</div>
</div>
</div>
</div>
Workaround code taken from popper.js. Doesn't seem to get doc.body right. The only one that specifically deals with TH TD TABLE. dom-helpers should work just because it uses offsetParent to traverse.
https://github.com/popperjs/popper-core/blob/master/src/dom-utils/getOffsetParent.js
var isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined' && typeof navigator !== 'undefined';
const isIE11 = isBrowser && !!(window.MSInputMethodContext && document.documentMode);
const isIE10 = isBrowser && /MSIE 10/.test(navigator.userAgent);
function isIE(version) {
if (version === 11) {
return isIE11;
}
if (version === 10) {
return isIE10;
}
return isIE11 || isIE10;
}
function getStyleComputedProperty(element, property) {
if (element.nodeType !== 1) {
return [];
}
// NOTE: 1 DOM access here
const window = element.ownerDocument.defaultView;
const css = window.getComputedStyle(element, null);
return property ? css[property] : css;
}
function getOffsetParent(element) {
if (!element) {
return document.documentElement;
}
const noOffsetParent = isIE(10) ? document.body : null;
// NOTE: 1 DOM access here
let offsetParent = element.offsetParent || null;
// Skip hidden elements which don't have an offsetParent
while (offsetParent === noOffsetParent && element.nextElementSibling) {
offsetParent = (element = element.nextElementSibling).offsetParent;
}
const nodeName = offsetParent && offsetParent.nodeName;
if (!nodeName || nodeName === 'BODY' || nodeName === 'HTML') {
return element ? element.ownerDocument.documentElement : document.documentElement;
}
// .offsetParent will return the closest TH, TD or TABLE in case
// no offsetParent is present, I hate this job...
if (['TH', 'TD', 'TABLE'].indexOf(offsetParent.nodeName) !== -1 && getStyleComputedProperty(offsetParent, 'position') === 'static') {
return getOffsetParent(offsetParent);
}
return offsetParent;
}
#header {
background-color: #ddd;
padding: 2rem;
}
#containing-block {
background-color: #eef;
padding: 2rem;
height: 70px;
transform: translate(0, 0);
}
#button {
position: fixed;
top: 50px;
}
<div id="header">header</div>
<div id="containing-block">
containing-block
<div>
<div>
<div>
<button id="button" onclick="console.log('offsetParent', getOffsetParent(this))">click me</button>
</div>
</div>
</div>
</div>
I recently crafted what I feel to be a rather elegant workaround to this not-so-little, long-standing quirk. I've designed a CustomElement that can automatically detect if it has been used inside of a containing block and, if so, shift itself from it's current location in the DOM to the end of the body element.
Credit to this answer to a similar question for pointing me in the right direction. https://stackoverflow.com/a/65155438/6036546
<!DOCTYPE html>
<title> Breakout Fixed </title>
<script type="module">
customElements.define(
'breakout-fixed',
class BreakoutFixed extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode : 'open' });
this.shadowRoot.innerHTML = this.template;
}
get template() {
return `
<style> :host { position: fixed; } </style>
<slot></slot>
`;
}
breakout() {
const el = this;
if (this.fixed !== true) {
window.addEventListener('resize', el.fix);
this.fixed = true;
}
if (el.parentNode == document.body) { return; }
function shift() {
getContainingBlock(el) &&
document.body.append(el);
}
function getContainingBlock(node) {
if (node.parentElement) {
if (node.parentElement == document.body) {
return document.body;
} else if (testNode(node.parentElement) == false) {
return getContainingBlock(node.parentElement);
} else { return node.parentElement; }
} else { return null; }
function testNode(node) {
let test; let cs = getComputedStyle(node);
test = cs.getPropertyValue('position'); if ([
'absolute', 'fixed'
].includes(test)) { return true; }
test = cs.getPropertyValue('transform'); if (test != 'none') { return true; }
test = cs.getPropertyValue('perspective'); if (test != 'none') { return true; }
test = cs.getPropertyValue('perspective'); if (test != 'none') { return true; }
test = cs.getPropertyValue('filter'); if (test != 'none') { return true; }
test = cs.getPropertyValue('contain'); if (test == 'paint') { return true; }
test = cs.getPropertyValue('will-change'); if ([
'transform', 'perspective', 'filter'
].includes(test)) { return true; }
return false;
}
}
}
connectedCallback() {
this.breakout();
}
}
);
</script>
<style>
body { background: dimgrey; }
#container {
height: 300px;
width: 50%;
background: dodgerblue;
transform: scale(2);
}
div#test {
position: fixed;
right: 0;
bottom: 0;
padding: 1rem;
background: red;
}
breakout-fixed {
top: 0; right: 0;
padding: 1rem;
background: limegreen;
transform: scale(3);
transform-origin: top right;
}
</style>
<div id="container">
<div id="test"> This element will be fixed to it's containing block. </div>
<breakout-fixed>
<div> This element will be fixed to the viewport. </div>
</breakout-fixed>
</div>

Categories