Unable to achieve 60fps animations - javascript

I REALLY want to achieve 60fps animations. Specifically, on my input elements on the focus event. The highest (according to Chrome Developer Tools) fps I can achieve is about 18fps.
I am using everything that is said to be best practice: window.requestAnimationFrame in my JavaScript, will-change in my css, and i'm using transforms for the actual animations. Even with all of these set in place, my animations aren't near 60fps.
var form = {};
function addActive(element) {
element.highlightElement.classList.add("input-highlight-active");
}
function removeActive(element) {
element.highlightElement.classList.remove("input-highlight-active");
}
// loops through the entire object removing "active" class on all elements except fo rthe element that is the target.
function setNewState(element, id, obj) {
for (var key in obj) {
if (id === key) {
window.requestAnimationFrame(() => addActive(element));
} else {
removeActive(obj[key]);
}
}
}
// Captures the event.target.id and corresponding object to be passed to setNewState function
function handleEvent(e) {
var active = form[e.target.id];
var activeId = e.target.id;
setNewState(active, activeId, form);
}
// Adds focus event listener to html elements
function addEvent() {
var inputElements = document.querySelectorAll('.input-wrapper > input');
inputElements = Array.from(inputElements);
inputElements.map(el => el.addEventListener('focus', (e) => handleEvent(e)));
}
// Creates object using input id as object key
function init() {
var temp = {};
var inputElements = document.querySelectorAll('.input-wrapper > input');
for (var i = 0; i < inputElements.length; i++) {
temp[inputElements[i].id] = {
highlightElement: inputElements[i].nextElementSibling,
active: false
};
}
return temp;
}
form = init();
addEvent();
body {
background-color: #121217;
}
.input-wrapper {
position: relative;
width: 60%;
margin: 0px auto 40px;
text-align: left;
}
.input-label {
position: relative;
text-align: left;
font-size: 18px;
font-weight: 300;
letter-spacing: 0.012em;
color: #eee;
}
input {
appearance: none;
-webkit-appearance: none;
position: relative;
width: 100%;
margin: 8px auto 0;
letter-spacing: 0.012em;
font-size: 18px;
color: #fafafa;
background-color: #121217;
border: 0px;
transform-origin: left;
border-bottom: 1px solid #fafafa;
}
input:focus {
outline: none;
}
.input-highlight {
will-change: transform;
position: absolute;
top: 99%;
width: 101%;
height: 2px;
background-color: #006daa;
z-index: 1;
transform-origin: left;
transform: scaleX(0);
transition: all 0.12s linear;
}
.input-highlight-active {
will-change: transform;
transform: scaleX(1);
transition: all 0.12s linear;
}
<div class="input-wrapper">
<div class="input-label">Full name</div>
<input type="text" id="fullName">
<div class="input-highlight"></div>
</div>
At the end of the day, I am sure i'm obsessing over something most won't notice. Ultimately, I just want to understand. Even if you tell me i've done all I can.

In the code you provided, you are using CSS animations, just by applying the extra class. There's no need to call requestAnimationFrame because it's designed to be called multiple times via the callback.
Since you took advantage of CSS animations you only need to add the class that changes the scale ONCE on focus, then you can remove it on blur:
.input-highlight-active {
transform: scaleX(1);
}
Because the input already has a class where the transition value specifies it will animate all properties (that it's able to animate), you only need to specify the property change that's animated.
Demo

Related

I get undefined error using pure javascript in my code

I am trying to add a material ripple effect to an element using pure javascript using the code below. The problem is that the effect works only for the first click.
If I try to click the button again, I get the message Uncaught TypeError: circle is undefined on my console.
function createRipple(event) {
const target = event.currentTarget;
const circle = target.classList.add("ripple");
const ripple = circle.getElementsByClassName("ripple")[0];
if (ripple) {
ripple.remove();
}
}
const buttons = document.getElementsByClassName("my-ripple");
for (const button of buttons) {
button.addEventListener("click", createRipple);
}
.my-ripple {
position: relative;
overflow: hidden;
display: inline-block;
width: auto;
padding: 1.2rem;
font-family: sans;
cursor: pointer;
border-radius: 10px;
}
.ripple::after {
content: "";
position: absolute;
border-radius: 50%;
transform: scale(0);
animation: ripple 600ms linear;
background-color: rgba(0, 0, 0, 0.5);
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
width: 100px;
height: 100px;
}
#keyframes ripple {
to {
transform: scale(4);
opacity: 0;
}
}
<div class="my-ripple">Click me</div>
classList does not return an object. And you do not need it actually, because you already have the target instance, so fetching it again by class is not necessary. Instead just remove the class from it directly with a timeout:
function createRipple(event) {
const target = event.currentTarget;
target.classList.add("ripple");
setTimeout(function() {
target.classList.remove("ripple");
}, 600);
}

Use Single button to toggle two functions for easy Light and Dark Mode

I have a code that allows me to easily switch from light mode to dark mode without having to write css for dark mode, it works with two buttons, each executing either the light mode or dark mode depending on your choice of click. However i want to merge this two functions into one single button so that it can be used to toggle the function. If anyone answering does include how it can be used with a toggle button, that won't be bad at all... The code i am using is
const htmlEl = document.getElementsByTagName('html')[0];
const toggleTheme = (theme) => {
htmlEl.dataset.theme = theme;
}
html {
transition: color 300ms, background-color 300ms !important;
}
html[data-theme='dark'] {
background: #000;
filter: invert(1) hue-rotate(180deg)
}
html[data-theme='dark'] img {
filter: invert(1) hue-rotate(180deg)
}
<button onclick="toggleTheme('dark');">Dark Mode Test</button>
<button onclick="toggleTheme('light');">Light Mode Test</button>
<br><font color="red">WE ARE RED</font>
<br><font color="blue">WE ARE BLUE</font>
<br><font color="black">WE ARE Black</font>
Then i tried to execute the switch using Eventlisteners this way
<script>
var btn = document.getElementById('navbtn');
btn.addEventListener('click', function() {
var foo = 'dark';
if (foo === "light") {
toggleTheme('dark');
} else {
toggleTheme('light');
}
}, false);
</script>
This however only reacts on first click and not on second. What could be the best way to achieve this? Thanks in anticipation.
Here ya go, with some flare from a switch I made awhile back anyway. If you want verification just open your dev tools and watch the data attribute of the example HTML change live.
const htmlEl = document.documentElement,
outputExample = document.getElementById('output');
let savedTheme = 'light';
// ********************************************************************
// NOTE: The localStorage example WILL NOT WORK HERE ON STACKOVERFLOW
// Instead it will throw a "SecurityError" and rightfully so...
// You'll have to use that part of the example in your
// own project to see it in action...... JUST FYI.
// So you will need to UNCOMMENT the commented out stuff for
// the localstorage example to work. Left it commented out for
// for other readers to not see the valid securityerror....
// ********************************************************************
// Toggle the theme and update their local storage.
toggleTheme = (bool) => {
const theme = bool ? 'light' : 'dark';
htmlEl.dataset.theme = theme;
// localStorage.setItem('savedTheme', theme);
outputExample.innerText = `${theme} theme`;
}
// Handle their saved preferred theme.
setSavedTheme = () => {
// If there is no current theme in localstorage then give them one as a default.
// Like for first time visitors...
// Uncomment this block and the localstorage piece above for the localstorage example.
/*
if (localStorage.getItem('savedTheme') === null) {
localStorage.setItem('savedTheme', savedTheme);
} else {
savedTheme = localStorage.getItem('savedTheme');
}
*/
htmlEl.dataset.theme = savedTheme;
outputExample.innerText = `${savedTheme} theme`;
}
// Set the default.
setSavedTheme();
.slide-toggle {
position: relative;
font-weight: 600;
display: inline-block;
border-radius: 3px;
}
.slide-toggle input:first-of-type {
position: absolute;
top: 0;
left: 0;
opacity: 0;
}
.slide-toggle input:first-of-type:focus ~ label {
box-shadow: 0 0 0 6px rgba(255, 0, 0, 0.15);
}
.slide-toggle input:first-of-type:checked ~ label {
color: #fff;
background-color: darkorange;
}
.slide-toggle input:first-of-type:checked ~ label:after {
transform: translateX(100%);
}
.slide-toggle input:first-of-type:disabled ~ label {
opacity: 0.5;
pointer-events: none;
}
.slide-toggle label:first-of-type {
display: flex;
height: 2rem;
width: 4rem;
flex-direction: row;
align-items: center;
justify-content: center;
cursor: pointer;
user-select: none;
outline: none;
color: #777;
background-color: #ddd;
border-radius: 3px;
transition: background-color 0.25s ease, box-shadow 0.15s ease, color 0.25s ease;
}
.slide-toggle label:first-of-type:hover {
border-color: #777;
box-shadow: 0 0 0 6px rgba(225, 0, 0, 0.15);
}
.slide-toggle label:first-of-type:after {
content: "";
position: absolute;
top: 3px;
bottom: 3px;
left: 3px;
right: 50%;
border-radius: 3px;
background-color: #fff;
transition: transform 0.25s cubic-bezier(0.6, 0.82, 0, 0.76);
}
.slide-toggle label:first-of-type:hover {
box-shadow: 0 0 0 6px rgba(225, 0, 0, 0.15);
}
.slide-toggle label:first-of-type div {
display: flex;
flex: 1;
align-items: center;
justify-content: center;
}
html {
transition: background-color .3s ease;
}
html[data-theme="light"] {
background-color: #fff;
}
html[data-theme="dark"] {
color: #fff;
background-color: #212121;
}
body {
height: 50vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.2/css/all.css">
<div class="slide-toggle">
<input id="guidLater"
type="checkbox" checked
onchange="toggleTheme(this.checked)"/>
<label for="guidLater">
<div><i class="fas fa-sun"></i></div>
<div><i class="fas fa-moon"></i></div>
</label>
</div>
<div id="output" style="margin-top: 1rem"></div>
btn.addEventListener('click', function() {
var foo = 'dark';
if (foo === "light") {
toggleTheme('dark');
} else {
toggleTheme('light');
}
}, false);
Is wrong.
You need to grab the current theme from the clicked on element (you have it set in data-theme attribute of the element).
Your code just sets the theme as "dark" every time a button is clicked, rather than actually accepting the current value, and toggling it.
btn.addEventListener('click', function(event) {
var currentTheme= event.currentTarget.dataset.theme;
if (currentTheme=== "light") {
toggleTheme('dark');
} else {
toggleTheme('light');
}
}, false);
Also, remove your inline calls of toggleTheme, and only call the function from the handler on the click event listener.
<button data-theme="dark">Dark Mode Test</button>
<button data-theme="light">Light Mode Test</button>

HTMLDivElement resizing inconsistent after WebpackDevServer build

I have a custom web component that I am trying to make which looks like this
But it looks like this after I reload the page
Notice the thin white div at the very bottom of each image.
I notice that I get the desired result (the fist image) immediately following a Webpack build triggered by saving this component's javascript file. Each time I reload the page, the second image is rendered on screen. What causes this behavior? I will attach the pertinent parts of my code below. (The styling for the large yellow and white components is done for privacy)
FeaturedProjectCard.js
const template = document.createElement('template')
template.innerHTML = `
<style>
.bottomDivider {
position: absolute;
bottom: 0;
width: 75%;
height: 1px;
margin: 0;
background-color: white;
}
.container {
position: relative;
width: 1000px;
isolation: isolate;
}
.description {
border-radius: 10px;
padding: 25px;
color: white;
background-color: white;
}
.linksContainer {
display: flex;
}
.linksContainer > a {
width: 20px;
padding: 10px;
color: white;
transition: color 0.5s;
}
.linksContainer > a:first-child {
padding-left: 0;
}
.linksContainer > a:hover {
color: yellow;
}
.projDesc {
width: 50%;
display: flex;
flex-direction: column;
pointer-events: none;
}
.projDesc > * {
width: fit-content;
pointer-events: auto;
}
.projImage {
position: absolute;
top: 50%;
right: 0;
height: 300px;
transform: translateY(-50%);
background-color: yellow;
border-radius: 5px;
z-index: -1;
}
.projImage > img {
height: inherit;
border-radius: inherit;
filter: grayscale(100%);
-webkit-filter: grayscale(100%);
opacity: 0;
transition: opacity 0.5s, filter 0.5s, -webkit-filter 0.5s;
}
.projImage > img:hover {
opacity: 1;
filter: grayscale(0%);
-webkit-filter: grayscale(0%);
}
.projectHeading {
margin: 10px 0;
color: yellow;
font-size: 12pt;
}
.projectName {
display: inline;
margin: 15px 0;
font-size: 21pt;
color: white;
}
.softwareContainer {
margin: 25px 0 10px 0;
color: white;
}
.softwareContainer > span:not(:last-child) {
padding-right: 30px;
}
</style>
<div class="container">
<div class="projDesc">
<span class="projectHeading">Recent Project</span>
<span class="projectName"></span>
<div class="description"></div>
<div class="softwareContainer"></div>
<div class="linksContainer"></div>
</div>
<div class="projImage">
<img>
</div>
<p class="bottomDivider"></p>
</div>
`
class FeaturedProjectCard extends HTMLElement {
constructor() {
super()
//Enable shadow DOM
this.attachShadow({ mode: 'open' })
this.shadowRoot.appendChild(template.content.cloneNode(true))
//Insert project image
this.shadowRoot.querySelector('img').src = this.getAttribute('image')
//Insert project heading
this.shadowRoot.querySelector('.projectName').textContent = this.getAttribute('heading')
//Insert project description
this.shadowRoot.querySelector('.description').innerHTML = //Get description text
//Insert software array
const softwareItems = //Get items
if(softwareItems) {
const softwareContainer = this.shadowRoot.querySelector('.softwareContainer')
for(let i = 0; i < softwareItems.length; i++)
softwareContainer.innerHTML += `<span>${softwareItems[i].textContent}</span>`
}
//Insert link array
const links = //Get links
if(links) {
const linksContainer = this.shadowRoot.querySelector('.linksContainer')
for(let i = 0; i < links.length; i++) {
linksContainer.innerHTML += `<a target="_blank" href="${links[i].getAttribute('target')}">${links[i].innerHTML}</a>`
}
}
//Handle reverse attribute
if(this.hasAttribute('reversed')) {
this.shadowRoot.querySelector('.bottomDivider').style.right = 0;
this.shadowRoot.lastElementChild.style.height = `${this.shadowRoot.lastElementChild.offsetHeight}px`
const projDesc = this.shadowRoot.querySelector('.projDesc')
const projImage = this.shadowRoot.querySelector('.projImage')
const softwareItems = Array.from(this.shadowRoot.querySelectorAll('.softwareContainer > span'))
projDesc.style.position = 'absolute'
projDesc.style.right = '0'
projDesc.style.left = 'auto'
projImage.style.left = '0'
projImage.style.right = 'auto'
projDesc.style.alignItems = 'flex-end'
softwareItems.splice(0, 1)[0].style.padding = '0'
const padding = getComputedStyle(softwareItems[0]).getPropertyValue('padding-right')
softwareItems.forEach(item => {
item.style.paddingLeft = padding
item.style.paddingRight = '0'
})
}
//Clear markup
this.innerHTML = null
}
}
window.customElements.define('featured-project-card', FeaturedProjectCard)
I use these component in HTML with the following syntax.
<featured-project-card class="" reversed image="/something.png" heading="Heading">
I think that the root cause of this behavior is the inclusion of the optional 'reversed' attribute. The other components that don't use this attribute are not affected by this issue since div.projDesc has position: static for those instances. This allows the div.container to resize. These images show the only component where this attribute is used. I will end this post with one final image of a component without the 'reversed' attribute for reference. In essence, I achieve the reverse effect by setting the div.projDesc properties position, right, and left to absolute, 0, and auto respectively. Consequently, I had to set the div.container height equal to what it was prior to these changes. Otherwise the element would not size correcly since position: absolute is being used on the only child with position: static
I used a ResizeObserver to see if the div.projDesc was resizing itself more than once and indeed it was. To wrap up, the 'reversed' div.projDesc is resizing itself strangely (likely because of how the 'reverse' attribute is handled) as opposed to non 'reversed' elements. I am aware that there may be an easier way to solve this problem and I am open to suggestions, however, I would like to pin down the source of this behavior in hopes of gaining a better understanding of the nuances of HTML, CSS, & JS. I am more than happy to clarify myself for anyone that is willing to lend a hand. Thank you in advance!
Non reversed component

Hiding an element in internet explorer leaves artifacts on the screen (IE 10-11)

We use custom selectbox elements for our page (for reasons), but in one specific place and only with IE (in my case version 10 and 11), there is a problem.
When you open the box and close it, sometimes the browser leaves visual artifacts of that element. If there is a button below the element (in the opened state), then those artifacts are still on top of that button UNTIL you hover over the button, then those artifacts disappear at that place.
I think everyone experienced this behaviour at some point in his life with windows or something else. Sorry if my description isn't perfect.
Here are some screenshots (The red boxes are censored text, sorry):
Open state:
Closed state with artifacts:
Hope my description is accurate, I desperately need a fix for this.
I can't reproduce it on my system, but our QA guy can reproduce it consistently. It also changes depending on the size of the browser. On some sizes it doesn't seem to happen at all. It must be some weird rendering glitch of IE.
I don't think it has anything to do with our custom selectbox element, because it happens only in this specific place and in our old design we experienced it in a different place with a completely different element. At that time I thought it's because the DOM is too complicated for IE, but this page has almost no elements.
EDIT: I Just found out that if I minimize the window and maximize it again, the artifacts disappear, which should confirm that this is a rendering glitch, right?
EDIT 2: Here is the code and css of our custom selectbox
HTML:
<div class="input_selectbox">
<label>Title</label>
<div class="input_selectbox__head" data-placeholder="">
<label>
<input type="hidden" name="id" value="5-10909">
Dummy 1
</label>
</div>
<div class="input_selectbox__body">
<label data-value="" class="">
No Option
</label>
<label id="5-10909" data-value="5-10909" class="input_selectbox__option--selected" data-default="true">
Dummy 1
</label>
<label id="5-12568" data-value="5-12568" class="">
Dummy 2
</label>
<label id="5-20001" data-value="5-20001" class="">
Dummy 3
</label>
<label id="5-20002" data-value="5-20002" class="">
Dummy 4
</label>
</div>
</div>
LESS:
.input_combobox, .input_selectbox {
display: block;
font-family: RobotoCondensed-Regular;
font-size: 14px;
width: 100%;
overflow: hidden;
&:focus {
outline: none;
}
label {
cursor: pointer;
}
}
.input_selectbox {
font-family: Roboto-Regular;
}
.input_selectbox__head {
border: 1px solid #colorBorderGrey;
transition: background-color 0.2s, border 0.3s;
overflow-x: hidden;
text-overflow: ellipsis;
display: flex;
background-color: #colorLightestGrey;
padding: 10px;
justify-content: space-between;
&:hover {
cursor: pointer;
border-color: #colorDarkGrey;
&:after {
color: #colorBlue;
}
}
label {
display: flex;
user-select: none;
pointer-events: none;
i {
margin-right: 10px;
}
}
&:after {
content: "\f078";
font-style: normal;
font-variant: normal;
text-rendering: auto;
font-family: "Font Awesome 5 Pro";
font-weight: 900;
pointer-events: none;
transition: color 0.3s, opacity 0.3s, background-color 0.2s;
display: block;
padding-left: 10px;
font-size: 16px;
}
}
.input_combobox--active, .input_selectbox--active {
.input_combobox__head, .input_selectbox__head {
background-color: #colorLightGrey;
&:hover {
border: 1px solid #colorBorderGrey;
&:after {
color: #colorDarkGrey;
}
}
&:after {
background-color: #colorLightGrey;
}
span {
background-color: #colorGreyHover;
}
}
}
.input_combobox__body, .input_selectbox__body {
display: none;
position: fixed;
box-shadow: rgba(0,0,0, 0.05) 0px 2px 5px 0px;
background-color: #colorWhite;
border: 1px solid #colorBorderGrey;
border-top: none;
max-height: 400px;
overflow-y: auto;
width: 100%;
z-index: 499;
label {
font-family: RobotoCondensed-Regular;
font-size: 16px;
display: block;
padding: 10px 20px;
transition: background-color 0.5s, color 0.3s;
border-bottom: 1px solid #colorBorderGrey;
&:hover {
background-color: #colorLightGrey;
}
&:last-child {
border-bottom: none;
}
i {
margin-right: 10px;
}
input[type=checkbox] {
display: none;
}
}
}
.input_combobox__body--top, .input_selectbox__body--top {
box-shadow: rgba(0, 0, 0, 0.1) 0px -2px 5px 0px;
}
.input_combobox__option--inactive, .input_selectbox__option--inactive {
opacity: 0.3;
}
Javascript:
function selectbox()
{
// click
$(document).on("click", ".input_selectbox__head", function ()
{
$(this).trigger("selectbox:toggle");
});
// toggle
$(document).on("selectbox:toggle", ".input_selectbox__head", function ()
{
if ($(this).parent().hasClass("input_selectbox--active"))
{
$(this).trigger("selectbox:close");
}
else
{
$(this).trigger("selectbox:open");
}
});
// open
$(document).on("selectbox:open", ".input_selectbox__head", function ()
{
var selectbox = $(this).closest(".input_selectbox");
if (!selectbox.hasClass("readonly") && !selectbox.hasClass("input--disabled"))
{
$(".input_selectbox--active .input_selectbox__head").trigger("selectbox:close");
// Positionierung
var header = selectbox.find(".input_selectbox__head");
var headerHeight = header.outerHeight();
var selectboxBody = selectbox.find(".input_selectbox__body");
var headerPositionX = header.offset().left;
var headerPositionY = header.offset().top - $(window).scrollTop();
var bodyPositionY = headerPositionY + headerHeight;
selectboxBody.removeClass("input_selectbox__body--top");
selectboxBody.css({
"top": bodyPositionY,
"left": headerPositionX,
"width": selectbox.width(),
"bottom": ""
});
selectbox.addClass("input_selectbox--active");
selectboxBody.show();
// check if offscreen
var isOut = isOutOfViewport(selectboxBody.get(0));
if (isOut.bottom)
{
selectboxBody.addClass("input_selectbox__body--top");
selectboxBody.css({
top: "",
bottom: ($(window).innerHeight() - headerPositionY - 1)
});
}
// close combobox on parent scroll
var scrollParent = getScrollParent(header[0]);
$(scrollParent).one("scroll", function ()
{
header.trigger("selectbox:close");
});
$(document).one("scroll", function ()
{
header.trigger("selectbox:close");
});
$(window).one("resize", function ()
{
header.trigger("selectbox:close");
});
}
});
// close
$(document).on("selectbox:close", ".input_selectbox__head", function ()
{
var selectbox = $(this).closest(".input_selectbox");
selectbox.removeClass("input_selectbox--active");
var selectboxBody = selectbox.find(".input_selectbox__body");
selectboxBody.hide();
});
// change option
$(document).on("click", ".input_selectbox__body > label", function ()
{
var label = $(this);
var value = label.attr("data-value");
var headerLabel = $(this).closest(".input_selectbox__body").siblings(".input_selectbox__head").children("label");
var name = headerLabel.find("input[type=hidden]").attr("name");
label.addClass("input_selectbox__option--selected").siblings().removeClass("input_selectbox__option--selected");
headerLabel.html(label.html());
headerLabel.append('<input type="hidden" name="' + name + '" value="' + value + '" />');
headerLabel.closest(".input_selectbox__head").trigger("selectbox:close");
$(this).closest(".input_selectbox").trigger("selectbox:change").trigger("change");
});
// close selectbox on outside click
$(document).ready(function ()
{
$(document).on("click touchstart", function (event)
{
if ($(event.target).closest('.input_selectbox--active').length == 0)
{
$(".input_selectbox--active .input_selectbox__head").trigger("selectbox:close");
}
});
});
// form reset
$(document).on("reset", "form", function ()
{
var selectboxes = $(this).find(".input_selectbox");
selectboxes.each(function ()
{
$(this).find(".input_selectbox__body").find("label[data-default=true]").click();
});
});
}

How to add dynamic listener and get value of target

I've created a custom dropdown and would like to get the text content of the clicked element within.
Dropdown elements are created dynamically as are the event listeners but the listeners seem not to be working correctly.
Dropdown example:
I can see the listeners on each div within the dev tools.
Event listener of child div:
The first div in the dropdown fills the input with it's value but the others do not.
(function() {
let departments = ['Accounting', 'Human Resources', 'IT', 'Warehouse'];
let element = document.getElementById('dd-Department');
departments.forEach(v => {
let div = document.createElement('div');
div.appendChild(document.createTextNode(v));
div.addEventListener('click', () => {
element.parentNode.querySelector('input').value = v;
});
element.appendChild(div);
});
})();
.form-question {
display: flex;
flex-direction: column;
justify-content: center;
margin: 0 0 3rem;
min-height: 3rem;
}
.form-question__title {
color: #342357;
font-size: 1.5rem;
padding: 1rem;
}
.input-container {
border-bottom: solid 2px #333333;
position: relative;
}
input[readonly] {
cursor: pointer;
}
.input-container input {
border: none;
box-sizing: border-box;
outline: 0;
padding: .75rem;
position: relative;
width: 100%;
}
.input-container:focus-within .dropdown {
transform: scaleY(1);
}
.dropdown {
background: #ffffff;
box-shadow: 0 5px 12px #333333;
left: 0;
max-height: 300px;
overflow-y: auto;
padding: 0;
position: absolute;
right: 0;
top: calc(100% + 2px);
transform: scaleY(0);
transform-origin: top;
transition: transform .3s;
z-index: 10;
}
.dropdown div {
border-bottom: 2px solid #777777;
cursor: pointer;
padding: 8px;
z-index: 20;
}
.dropdown div:hover {
background: #dddddd;
font-weight: 800;
}
<div class="form-question">
<div class="form-question__title">
<span>Department</span>
</div>
<div class="form-question--dropdown input-container">
<input type="text" name="Department" readonly="readonly"></input>
<div id="dd-Department" class="dropdown"></div>
</div>
</div>
I also took a stab at event delegation, but could not get the text content of the clicked div. The target is the parent of the intended div, thus the text content was all child values combined.
let element = document.getElementById('dd-Department');
element.addEventListener('click', e => {
if (e.target && e.target.classList.contains('dropdown')) {
e.target.parentNode.parentNode.querySelector('input').value = e.target.textContent;
}
}, true);
Event Delegation on click of child div:
Am I missing something here?
UPDATE
Thank you #dawn for pointing out css as the problem.
I've worked around this by changing
.input-container:focus-within .dropdown
to
.input-container.active .dropdown
and adding the active class with javascript.
document.querySelectorAll('.input-container').forEach(v => {
v.onclick = () => v.classList.toggle('active');
});
Issue now is that on click of anything other than the input-container the dropdown is still active.
The following works but feels like a hack.
document.querySelectorAll('.input-container').forEach(v => {
v.addEventListener('focus', () => v.classList.add('active'), true);
v.addEventListener('blur', () => setTimeout(() => v.classList.remove('active'), 75), true);
});
Are there more elegant solutions?
This situation is a problem with css,When you click on the div,The first thing that triggers is "transform: scaleY(0)" and the ".dropdown" has invisible,so Cannot trigger click event.
Don't use input:focus-within to control the Visibilityof the drop-down box, because when you click the drop-down box, the input has lost focus.

Categories

Resources