JavaScript Drag and Drop Swap Items - javascript

I'm trying to create a simple drag & drop example to use that will allow swapping of items. For example:
Item 0
Item 1
Item 2
Item 3
If I drag and drop "Item 0" over "Item 3" they should swap places. What I have below does not swap the correct elements, will also make some slots "un-droppable" and error out due to e.dataTransfer not providing any data.
const log = console.log.bind(console);
const $ = document.getElementById.bind(document);
function drop(e) {
e.preventDefault();
let dragindex = 0;
let clone = e.target.cloneNode(true);
let data = e.dataTransfer.getData("text/plain");
if (clone.dataset.id !== data) {
[...$("container").children].forEach((el, i) => {
if (el.dataset.id == data) {
dragindex = +i;
}
})
log(data, clone.dataset.id, dragindex, e.target.dataset.id);
$("container").replaceChild(document.querySelector(`[data-id=${data}]`), e.target);
$("container").insertBefore(clone, $("container").childNodes[dragindex]);
}
}
[...document.querySelectorAll(".draggable")].map((el) => {
el.setAttribute("draggable", true);
});
[...document.querySelectorAll(".draggable")].map((el) => {
el.addEventListener("dragover", (e) => {
e.preventDefault();
})
el.addEventListener("dragstart", (e) => {
e.dataTransfer.setData("text/plain", e.target.dataset.id);
});
el.addEventListener("drop", (e) => {
drop(e);
});
})
#container {
width: 200px;
height: auto;
position: absolute;
left: 50%;
top: 50%;
background: dodgerblue;
color: #fff;
transform: translate(-50%, -50%);
border: 1px solid #000;
}
.draggable {
display: flex;
justify-content: start;
align-items: center;
border: 1px solid #fff;
margin: 2px;
padding: .5em;
text-align: center;
cursor: grab;
}
.draggable i {
margin-right: 25px;
}
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<div id="container">
<div class="draggable" data-id="drag0"><i class="material-icons">drag_indicator</i>Draggable 0</div>
<div class="draggable" data-id="drag1"><i class="material-icons">drag_indicator</i>Draggable 1</div>
<div class="draggable" data-id="drag2"><i class="material-icons">drag_indicator</i>Draggable 2</div>
<div class="draggable" data-id="drag3"><i class="material-icons">drag_indicator</i>Draggable 3</div>
</div>

So.... I finally cracked it. It was several things.
You have to re-add event listeners to a cloned node if they were added via "document.addEventListener()".
Had to use ".childNodes" not ".children" as the indexing does not work out the same.
Had to take care where/when I created the variable holding the reference node.
Also figured out that it acts weird in Safari on IOS if the drag/drop parent container is absolutely positioned so need to use flex positioning, as well as a few other minor details; any how, in case any one finds it helpful, here is the working code. Works in Android-Chrome/IOS-Safari.
const log = console.log.bind(console);
const $ = document.getElementById.bind(document);
function drop(e) {
e.preventDefault();
let dragindex = 0;
let referenceNode = "";
let clone = e.target.cloneNode(true);
addListeners(clone);
let data = e.dataTransfer.getData("text/plain");
if (clone.dataset.id !== data) {
[...$("container").childNodes].forEach((el, i) => {
if (el.dataset?.id == data) {
dragindex = i;
}
});
$("container").replaceChild(
document.querySelector(`[data-id=${data}]`),
e.target
);
referenceNode = $("container").childNodes[dragindex];
$("container").insertBefore(clone, referenceNode);
clone.classList.remove("dragActive");
}
}
function addListeners(el) {
el.addEventListener("dragover", (e) => {
e.preventDefault();
e.target.classList.add("dragActive");
});
el.addEventListener("dragstart", (e) => {
e.dataTransfer.setData("text/plain", e.target.dataset.id);
});
el.addEventListener("dragleave", (e) => {
e.target.classList.remove("dragActive");
});
el.addEventListener("dragend", (e) => {
e.target.classList.remove("dragActive");
});
el.addEventListener("drop", (e) => {
e.target.classList.remove("dragActive");
drop(e);
});
}
[...document.querySelectorAll(".draggable")].map((el) => {
el.setAttribute("draggable", true);
});
[...document.querySelectorAll(".draggable")].map((el) => {
addListeners(el);
});
html,
body {
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
}
#container {
width: 200px;
height: auto;
background: dodgerblue;
color: #fff;
border: 1px solid #000;
position: absolute;
/* top: 50%;
left: 50%;
transform: translate(-50%, -50%); */
}
.draggable {
border: 1px solid #fff;
margin: 2px;
padding: .5em;
text-align: center;
cursor: grab;
color: #000;
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAzCAYAAAAdD7HCAAAACXBIWXMAAA7DAAAOwwHHb6hkAAADGElEQVRYhe1Zy2oUURA9MSoiWYQRxEdAMBoDxpVGZhFQF5qV+AgSPyK6CPgFbswvCD4WrkQQHwsFDUY3JuomISjic8CZhYk6PtCoUev07Rlm2p7uPj2TXR84zUxSValO31t16nYb6rHM2G/sMf40PjG+QGvQaTxo7DX+Nk4Z7xp/hBkfMr4y/g1w3LityURGjV9CYheMg0HjkRDDWpaNO1MmMhYTexHuH+Ghz/grxoF8aVwpJjKQIC45b8zR4VJCB/K4mMxVIfYJLtg9QvBBMZndgu1eJtMlOKyFhpxg28FkCoJDCRreCLZvmcwdweEmNFwTbK/wssW4gPgFNgNXFBXwMb1PEHui1mkY0du75CedBnnjx4jY08Z1QSfuqsmAIRO8DG2Rh2GT8SLqq3DReNq4umLUFuK42dgN1z+Y9TxaBxbN9X7sd8iQIcMSIWxrrzH2+p+foXVbm9V7l3GH/30WTnr+CTOmtGTvofKqLXrXjd1NJrLP/+PB6jvr/64OLNllNC7ZLOdpZecQolsNb/5YxbgDrvfENbOSb6uAPaecIPY32rbb5aTxaILATGTO+FBI5pTxQAK7FXCjER4guU69DQ1K7EmucEUabhCT6RRsPdn5VXD4AA1FwXZOlZ33oOGWYHuDF654rua4Z1pGiCKLQaqdStm5GGHMOjGEdOBBQpTs5E3mg05SlRSxFa6S1xY/3jwrfvVQIexIhFPgdv87J4LHaNA/UmCp+l6GDBnq0N7g5xvhGuh3uHrQKnBrs3T0+HHLjQw583L2ZXOrFCbOxufhZuVmwFmdM3tQ8XG2/+/kjD1nGo1LNotTPmUiPL2I6k9McLjWYQLxzYznLDkxEVb0mQSxF/ykPVmYVI2dEZM5LMQ+R4ezgsNTMZkLQmzvTE9ZnOqhkSJTu1TZ+RkaFNlZZDL3BYcpaFAkrXfIyB3CrZvkuQ6IyfDY7HWCuNzefRWnI4iWneQY0oFjcdxUORJ04nuBQoghq/AomgOl5XhIbL7fqr7eCcrOVcb9cEcXy+GkIUeIT2gNWNj6/djPjY9QlbTAPz6/t2nPvICTAAAAAElFTkSuQmCC');
background-repeat: no-repeat;
background-size: 15px;
background-position: 5px;
position: relative;
}
.dragActive {
background: rgba(255, 255, 255, .25);
color: #000;
border: 1px solid #000;
}
<div id="container">
<div class="draggable" data-id="drag0">Draggable 0</div>
<div class="draggable" data-id="drag1">Draggable 1</div>
<div class="draggable" data-id="drag2">Draggable 2</div>
<div class="draggable" data-id="drag3">Draggable 3</div>
</div>

Related

How to use javascript to open the modal when connecting the API so that the user can see the animation effect?

let btn = document.querySelector('.btn');
let modal = document.querySelector('.modal');
let wrap = document.querySelector('.wrap');
let photo = document.querySelector('.photo');
let svg = document.querySelector('svg');
let data = [{
title: "dog",
age: 10,
rank: [{
rankStatus: 'behind'
},
{
rankStatus: 'generally',
rankNum: '20'
},
{
rankStatus: 'excellent'
}
]
}]
let rankStatusArr = data[0].rank.map(item => item.rankStatus);
let rankNum = data[0].rank.find(item => item.rankNum).rankNum;
btn.addEventListener('click', function(e) {
svg.style.display = "block"
axios.get("https://dog.ceo/api/breeds/image/random")
.then((response) => {
// 设置处理程序以在加载图像后显示图像
photo.addEventListener('load', () => {
svg.style.display = "none"
modal.style.display = "flex";
});
// 添加“加载”处理程序后设置图像源
photo.src = response.data.message;
})
.catch((error) => console.log(error));
// 排名狀態在 popup 打開後再載入動畫
let progress = rankNum;
let dot = document.getElementById("dot");
var dotPosition = (progress / 30) * 100;
dot.style.left = dotPosition + "%";
let txt = document.querySelectorAll('.txt');
for (let i = 0; i < txt.length; i++) {
txt[i].innerHTML = rankStatusArr[i];
}
})
wrap.addEventListener('click', function(e) {
e.stopPropagation();
})
modal.addEventListener('click', function() {
modal.style.display = "none"
})
.modal {
width: 100%;
height: 100vh;
background-color: rgba(0, 0, 0, 0.5);
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: none;
justify-content: center;
align-items: center;
}
.wrap {
width: 300px;
height: 300px;
padding: 20px;
background-color: #fff;
display: flex;
justify-content: center;
align-items: center;
border-radius: 20px;
flex-direction: column;
}
.wrap .photo {
width: 100%;
height: 200px;
object-fit: cover;
margin-bottom: 20px;
}
.wrap #progress-bar-container {
width: 100%;
height: 5px;
background-color: #fff;
position: relative;
display: flex;
flex-wrap: nowrap;
}
.wrap #progress-bar-container .progress-bar {
width: 33.33%;
height: 5px;
background-color: #ccc;
margin-right: 8px;
border-radius: 20px;
}
.wrap #progress-bar-container .progress-bar .txt {
text-align: center;
color: #ccc;
margin-top: 20px;
}
#progress-bar-1 {
background-color: #ddd;
}
#progress-bar-2 {
background-color: #ddd;
}
#progress-bar-3 {
background-color: #ddd;
margin-right: 0;
}
#dot {
width: 10px;
height: 10px;
border-radius: 50%;
background-color: #333;
position: absolute;
top: -3px;
left: 0;
transition: left 0.2s ease-out;
}
svg {
display: none;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
svg .load {
stroke-dasharray: 0 500;
animation: rot 1s linear infinite;
}
#keyframes rot {
100% {
stroke-dasharray: 500 500;
}
}
.green {
color: yellowgreen;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/1.3.2/axios.min.js"></script>
<button class='btn'>open</button>
<div class="modal">
<div class="wrap">
<img class="photo" src="" alt="photo">
<div id="progress-bar-container">
<div class="progress-bar" id="progress-bar-1">
<p class="txt">1</p>
</div>
<div class="progress-bar" id="progress-bar-2">
<p class="txt">2</p>
</div>
<div class="progress-bar" id="progress-bar-3">
<p class="txt">3</p>
</div>
<div id="dot"></div>
</div>
</div>
</div>
<!-- load -->
<svg width="240px" height="240px" version="1.1" xmlns="http://www.w3.org/2000/svg">
<circle cx="110" cy="110" r="90" stroke-width="10" stroke="gainsboro" fill="none"></circle>
<circle cx="110" cy="110" r="90" stroke-width="10" stroke="darkturquoise" fill="none" class="load"></circle>
</svg>
I encountered a little difficulty in the following requirements. The first difficulty is that I hope that the popup will not be opened until the picture is fully loaded! But I hope that when the popup is opened, the dot can add animation and run to the specified progress position, but it seems that the animation I want to appear has already run before it is opened, and the user will not see the moving animation effect. The second problem is that I want to add a green color to the rankStatus font on the screen if the rankNum is included in the rankStatus object, but I am a novice in programming and don't know which section to add to achieve this. Hope to get your help, thank you.
To solve the first difficulty, you can add an event listener to the photo element which will be triggered when the image is loaded. Inside this event listener, you can set the display of the modal to 'flex' and the display of the svg element to 'none'.
To solve the second difficulty, you can use an if statement inside your rankStatusArr loop. This statement should check if the rankNum is included in the rankStatus object and if so, add a class to the txt element which adds the green color style.
let txt = document.querySelectorAll('.txt');
for (let i = 0; i < txt.length; i++) {
txt[i].innerHTML = rankStatusArr[i];
if (rankNum === rankStatusArr[i]) {
txt[i].classList.add('green');
}
}

Template Literal Function not working as expected

I have a function that turns an array into modal window links as a template literal.
The code that creates the links works fine outside of the function
But once it gets rendered in the function it no longer works. I can't find any errors, but it does NOT work.
However, if I copy the HTML that the function renders and save that as actual HTML, that works fine on its own.
A good chunk of the JavaScript portion of the code is posted below. A full version is on Codepen.
There are two sections in the example on Codepen:
The first section has the code as it's rendered by the function.
The second section is copied from the Elements tab in Developer Tools and saved as actual HTML.
"use strict";
const modalBtns = document.querySelectorAll(".modal-button");
const modalWin = document.querySelector(".modal-window");
const closeBtn = document.querySelector(".close-modal");
const modal_iframe = document.getElementById("modal_iframe");
modalBtns.forEach((item) => {
item.addEventListener("click", function (e) {
let modal = e.currentTarget;
if (modal.dataset.target) {
let modalID = modal.dataset.target;
document.getElementById(modalID).style.display = "block";
}
if (modal.dataset.iframe) {
modal_iframe.src = modal.dataset.iframe;
document
.querySelector(".button-footer")
.addEventListener("click", function () {
window.open(modal.dataset.iframe, "_blank");
});
}
if (modal.dataset.header) {
document.querySelector(
".modal-header"
).innerHTML = `<h1>${modal.dataset.header}</h1>`;
}
if (modal.dataset.dimensions) {
document
.querySelector(".modal-window")
.setAttribute("style", modal.dataset.dimensions);
}
function loadIframe() {
let frame = document.getElementById("modal_window");
frame.style.height =
frame.contentWindow.document.body.scrollHeight + "px";
}
if (document.querySelector("#modal_window")) {
setTimeout(function () {
loadIframe;
}, 2000);
}
if (modal.dataset.reload && modal.dataset.reload === "1") {
document
.querySelector(".close-modal")
.addEventListener("click", function (e) {
console.log("parent.location.reload() pending...");
parent.location.reload();
});
}
/*======= All EventListeners Below Close Modal ================*/
closeBtn.addEventListener("click", function (e) {
document.querySelector(".modal-background").style.display = "none";
});
window.addEventListener("click", function (e) {
if (e.currentTarget === document.querySelector(".modal-background")) {
document.querySelector(".modal-background").style.display = "none";
}
});
document.body.addEventListener("keydown", (e) => {
if (e.key === "Escape") {
document.querySelector(".modal-background").style.display = "none";
}
});
});
});
const main = document.querySelector("main");
const modal_links = [
{
link: "https://notation.netcentrx.net/staff/",
header: "Musical Staff",
thb: "notation",
w_h: "min-width:60vw;max-width:600px;height:650px",
reload: 0
},
{
link: "https://wsl.netcentrx.net/",
header: "WSL Commands",
thb: "wsl",
w_h: "min-width:60vw;max-width:600px;height:650px",
reload: 0
}
];
let modalLink = "";
function createModalLinks(
link,
modalID,
header,
img,
w_h = "width:90vw;height:600px",
reload = "0"
) {
modalLink = `
<a href="javascript:void(0)" class="modal-button" onclick="console.log('onclick handler:${link}');" data-header="${header}" data-target="${modalID}" data-iframe="${link}" data-dimensions="${w_h};margin-top:20px" data-reload="${reload}">
<img src="https://resume.netcentrx.net/examples/${img}.jpg" title="${img}" width="50">
</a>
`;
return modalLink;
}
let theLinks = "";
modal_links.forEach((item) => {
theLinks += createModalLinks(
item.link,
"modal_window",
item.header,
item.thb,
item.w_h,
item.reload
);
});
main.innerHTML = theLinks;
My apologies in advance for it not being stripped down to just the bare minimum. But in order to replicate the problem, it required more code than it probably should have had. I've been reworking this for the better part of a day without any insight as to what the real problem is. I've been creating functions using template literals just like this for years now, usually with a high success rate. Whatever the problem is, I need to know so I can get past it. The only anomaly that I spotted is that–in the version on Codepen–the only thing that doesn't work in that version is once the modal is displayed clicking on the background does not dismiss the modal like it does elsewhere. If that's significant as to what the problem may be, I'm not sure what the connection is.
Usually when I take the time to painstakingly write everything out like this I typically either spot the problem or figure out an alternative solution so there's no need to actually post a question, but this does not appear to be one of those times. As always, your help is very much appreciated!
The issue appears to just be timing. Your code is executed in order, and the first part gets all of the modal buttons on the page and sets the appropriate event listeners. Then the second part of your code adds 2 modal buttons, which were not present earlier.
By simply wrapping the first part of your code in a function and calling it later (or swapping the order of those two parts of code), everything works as expected.
"use strict";
const _InitModal = () => {
const modalBtns = document.querySelectorAll(".modal-button");
const modalWin = document.querySelector(".modal-window");
const closeBtn = document.querySelector(".close-modal");
const modal_iframe = document.getElementById("modal_iframe");
modalBtns.forEach((item) => {
item.addEventListener("click", function (e) {
console.log("e.currentTarget = " + e.currentTarget);
let modal = e.currentTarget;
console.log("modal = " + modal);
if (modal.dataset.target) {
let modalID = modal.dataset.target;
console.log("modal.dataset.target = " + modal.dataset.target);
document.getElementById(modalID).style.display = "block";
}
if (modal.dataset.iframe) {
modal_iframe.src = modal.dataset.iframe;
document
.querySelector(".button-footer")
.addEventListener("click", function () {
window.open(modal.dataset.iframe, "_blank");
});
}
if (modal.dataset.header) {
document.querySelector(
".modal-header"
).innerHTML = `<h1>${modal.dataset.header}</h1>`;
console.log(`modal.dataset.header = ${modal.dataset.header}`);
}
if (modal.dataset.dimensions) {
document
.querySelector(".modal-window")
.setAttribute("style", modal.dataset.dimensions);
}
function loadIframe() {
let frame = document.getElementById("modal_window");
frame.style.height =
frame.contentWindow.document.body.scrollHeight + "px";
}
if (document.querySelector("#modal_window")) {
setTimeout(function () {
loadIframe;
}, 2000);
}
// e.preventDefault();
if (modal.dataset.reload && modal.dataset.reload === "1") {
document
.querySelector(".close-modal")
.addEventListener("click", function (e) {
console.log("parent.location.reload() pending...");
parent.location.reload();
});
}
/*======= All EventListeners Below Close Modal ================*/
closeBtn.addEventListener("click", function (e) {
document.querySelector(".modal-background").style.display = "none";
});
window.addEventListener("click", function (e) {
console.log("e.currentTarget = " + e.currentTarget);
if (e.currentTarget === document.querySelector(".modal-background")) {
document.querySelector(".modal-background").style.display = "none";
}
});
document.body.addEventListener("keydown", (e) => {
if (e.key === "Escape") {
console.log("e=" + e);
document.querySelector(".modal-background").style.display = "none";
}
});
});
});
}
const main = document.querySelector("main");
const modal_links = [
{
link: "https://notation.netcentrx.net/staff/",
header: "Musical Staff",
thb: "notation",
w_h: "min-width:60vw;max-width:600px;height:650px",
reload: 0
},
{
link: "https://wsl.netcentrx.net/",
header: "WSL Commands",
thb: "wsl",
w_h: "min-width:60vw;max-width:600px;height:650px",
reload: 0
}
];
function createModalLinks(
link,
modalID,
header,
img,
w_h = "width:90vw;height:600px",
reload = "0"
) {
let modalLink = "";
modalLink = `
<a href="javascript:void(0)" class="modal-button" onclick="console.log('onclick handler:${link}');" data-header="${header}" data-target="${modalID}" data-iframe="${link}" data-dimensions="${w_h};margin-top:20px" data-reload="${reload}">
<img src="https://resume.netcentrx.net/examples/${img}.jpg" title="${img}" width="50">
</a>`;
return modalLink;
}
let theLinks = "";
modal_links.forEach((item) => {
theLinks += createModalLinks(
item.link,
"modal_window",
item.header,
item.thb,
item.w_h,
item.reload
);
});
main.innerHTML = theLinks;
_InitModal();
.modal-background {
font-family: sans-serif;
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
display: none;
overflow: auto;
background-color: rgba(0, 0, 0, 0.9);
z-index: 9999;
background: rgba(55, 55, 55, 0.6);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
.modal-window {
position: relative;
background-color: #ffffff;
width: 50%;
margin: 10% auto;
border-radius: 0.5rem;
padding: 0.75rem;
border: 1px groove #ccc;
/* box-shadow: 1px 1px 1px #999, 2px 2px 2px #000; */
}
.close-modal:hover,
.close-modal:focus {
color: rgba(255, 255, 255, 1);
cursor: pointer;
background: red;
transition: 1s;
text-shadow: 1px 1px 1px #999, 2px 2px 2px #000;
}
button.close-modal {
position: absolute;
top: -0.75rem;
right: -0.75rem;
padding: 0.05rem 0.75rem;
background: #999;
color: #ccc;
border-radius: 50%;
border: none;
outline: none;
-webkit-transition: all 0.5s cubic-bezier(0.23, 1, 0.32, 1);
transition: all 0.5s cubic-bezier(0.23, 1, 0.32, 1);
-webkit-animation-name: animatebottom;
-webkit-animation-duration: 1.5s;
animation-name: animatebottom;
animation-duration: 1.5s;
}
button.close-modal::before {
content: "\D7";
font-size: 2rem;
}
.modal-window {
display: flex;
flex-direction: column;
flex-wrap: nowrap;
-webkit-animation-name: animatetop;
-webkit-animation-duration: 0.5s;
animation-name: animatetop;
animation-duration: 0.5s;
}
.modal-header {
height: 30px;
text-align: center;
width: 100%;
background: #fff;
padding: 0.2rem;
}
.modal-header h1 {
font-size: 1.1rem;
}
.modal-footer {
height: 20px;
text-align: center;
width: 100%;
background: #fff;
padding: 0.2rem;
}
.modal-content {
background-color: #fff;
height: calc(100% - 70px);
border-radius: 0.5rem;
border: 0.1rem groove #ddd;
overflow: hidden;
}
.button-footer {
background: #fff;
border-radius: 0.5rem;
border: 1px outset #aaa;
padding: 0.2rem;
color: #999;
transition: 1s;
cursor: pointer;
}
.button-footer:hover {
background: #fdfdfd;
color: #555;
border: 1px inset #ddd;
text-shadow: 0.05rem 0.05rem 0.05rem #ccc, 0.055rem 0.055rem 0.055rem #999,
0.06rem 0.06rem 0.06rem #333;
transition: 1s;
}
.close-btn:hover {
color: white;
background: #f00;
cursor: pointer;
}
#modal_iframe {
width: 100%;
height: 100%;
}
button.modal-button {
border-radius: 0.5rem;
border: 0px solid #aaa;
padding: 0;
cursor: pointer;
}
.modal-button-img {
border-radius: 0.5rem;
border: 0.1rem groove #444;
cursor: pointer;
}
.sepia:hover {
filter: sepia(150%);
}
/*
.none {
display: none;
}
*/
#-webkit-keyframes animatetop {
from {
top: -300px;
opacity: 0;
}
to {
top: 0;
opacity: 1;
}
}
#keyframes animatetop {
from {
top: -300px;
opacity: 0;
}
to {
top: 0;
opacity: 1;
}
}
#-webkit-keyframes animatebottom {
from {
top: 0;
opacity: 1;
}
to {
bottom: -300px;
opacity: 0;
}
}
#keyframes animatebottom {
from {
top: 0;
opacity: 1;
}
to {
bottom: -300px;
opacity: 0;
}
}
.container {
border-radius: 0.5rem;
border: 1px solid #aaa;
max-width: 800px;
width: 500px;
margin: 0 auto;
text-align: center;
font-family: sans-serif;
}
main,
aside {
font-family: sans-serif;
max-width: 800px;
width: 500px;
margin: 0 auto;
text-align: center;
}
h2 {
text-align: center;
font-family: sans-serif;
font-weight: normal;
font-size: 1.2rem;
}
span {
font-size: 75%;
background: #ffff0055;
}
<div id="modal_window" class="modal-background">
<div class="modal-window">
<button class="close-modal" data-dismiss="modal"></button>
<div class="modal-header"></div>
<div class="modal-content">
<iframe src="#" id="modal_iframe" frameborder="0">If you'd have had a real browser, I wouldn't be boring you with this now...</iframe>
</div>
<div class="modal-footer"><button class="button-footer">Open In New Tab</button></div>
</div>
</div>
<div class="container">
<h2><code>main</code> Content Rendered By JavaScript</h2>
<main>
Main
</main>
<span>working now</span>
</div>

How to dynamically change color of clicked elements using color-picker

I wanted to change color according to user preference dynamically like when a user selects a color then it is applied synchronously to the element .
Like when an element is clicked color picker opens and then it works like developer tools color-picker , when a color is chosen from the picker it is applied to element and if user wants to change the color again in same picker than that also applied
Tried to went through following questions but couldn't find answer :
Change background colors dynamically using input event
how to dynamically select a color from a color picker by using jQuery?
HTML Input Color Picker, Apply Changes In Sync With Color Picker Selection
I wanted to code work like this in below snippet, whichever element is clicked than colors are changes of that element.
In original code html is like this :
<div id="clockOuterCircle"><div id="clockStyleCircle"></div></div> which can be solved by bubbling/capturing
var reed = document.getElementById("clockOuterCircle");
var reed1 = document.getElementById("clockStyleCircle");
reed.addEventListener('click', deed)
reed1.addEventListener('click', deed)
function deed() {
var reed2 = document.getElementById("colorClock");
reed2.click();
var reed3 = reed2.value;
// reed1.addEventListener('change', function() {
this.style.backgroundColor = reed3;
document.getElementById("demo").innerHTML = reed3;
//})
}
#clockStyleCircle {
position: absolute;
width: 16vw;
height: 16vw;
text-align: center;
padding: 0%;
top: 28.5%;
left: 28.5%;
border-radius: 50%;
border: 3px solid black;
background-color: rgb(255, 233, 35);
}
#clockOuterCircle {
display: flex;
justify-content: center;
align-items: center;
width: 42vw;
height: 42vw;
margin: auto;
border-radius: 50%;
border: 4px solid rgb(255, 62, 62);
background-color: rgb(253, 133, 133);
user-select: none;
}
<div id="clockStyleCircle"></div>
<div id="clockOuterCircle"></div>
<div id="demo"></div>
<input type="color" name="colorClock" id="colorClock">
Possible answer of dynamically changing color can be like this in below snippet, like using
input event separately on each element.
var reed = document.getElementById("clockOuterCircle");
var reed1 = document.getElementById("clockStyleCircle");
reed.addEventListener('click', deed)
reed1.addEventListener('click', deed)
//function deed() {
// var reed2 = document.getElementById("colorClock");
// reed2.click();
// var reed3 = reed2.value;
// reed1.addEventListener('change', function() {
// this.style.backgroundColor = reed3;
// document.getElementById("demo").innerHTML = reed3;
//})
//}
reed2 = document.getElementById("colorClock");
reed2.addEventListener('input', deed)
function deed() {
var reed3 = reed2.value;
reed.style.backgroundColor = reed3;
}
reed4 = document.getElementById("colorClock2");
reed4.addEventListener('input', deed1)
function deed1() {
var reed5 = reed4.value;
reed1.style.backgroundColor = reed5;
}
#clockStyleCircle {
position: absolute;
width: 16vw;
height: 16vw;
text-align: center;
padding: 0%;
top: 28.5%;
left: 28.5%;
border-radius: 50%;
border: 3px solid black;
background-color: rgb(255, 233, 35);
}
#clockOuterCircle {
display: flex;
justify-content: center;
align-items: center;
width: 42vw;
height: 42vw;
margin: auto;
border-radius: 50%;
border: 4px solid rgb(255, 62, 62);
background-color: rgb(253, 133, 133);
user-select: none;
}
<div id="clockStyleCircle"></div>
<div id="clockOuterCircle"></div>
<div id="demo"></div>
<input type="color" name="colorClock" id="colorClock">
<input type="color" name="colorClock" id="colorClock2">
But in above method color is not changing of clicked element instead a fixed element color is changed which is defined beforehand . So have to apply code to different elements separately, as there are many elements so wanted to apply both ways
Thanks for the help in advance.
Updated to stop bubbling with event.stopPropagation()
If I've understood you correctly, you want to launch a colour picker every time any particular element in your page is clicked which allows you to change that element's background colour.
This solution adds and then launches a colour picker when any element with the class circle is clicked, then removes it again after a colour has been selected.
The colour picker input is hidden with display:none but the dialog box is visible.
let body = document.body;
let circles = document.querySelectorAll('.circle');
circles.forEach((circle) => {
circle.addEventListener('click', () => {
let existingColourPickers = document.querySelectorAll('input.colour-picker')
existingColourPickers.forEach((existingColourPicker) => {
if (body.contains(existingColourPicker)) {
body.removeChild(existingColourPicker);
}
});
event.stopPropagation();
let colourPicker = document.createElement("input");
colourPicker.type = "color";
colourPicker.className = "colour-picker";
body.appendChild(colourPicker);
colourPicker.click();
colourPicker.addEventListener("input", () => {
circle.style.backgroundColor = event.target.value;
}, false);
colourPicker.addEventListener("change", () => {
body.removeChild(colourPicker);
}, false);
});
});
#clockStyleCircle {
position: absolute;
width: 16vw;
height: 16vw;
text-align: center;
padding: 0%;
top: 28.5%;
left: 28.5%;
border-radius: 50%;
border: 3px solid black;
background-color: rgb(255, 233, 35);
z-index:1;
}
#clockOuterCircle {
display: flex;
justify-content: center;
align-items: center;
width: 42vw;
height: 42vw;
margin: auto;
border-radius: 50%;
border: 4px solid rgb(255, 62, 62);
background-color: rgb(253, 133, 133);
user-select: none;
}
#another-circle {
width:50px;
height:50px;
border-radius: 50%;
border: 4px green solid;
background-color:blue;
position:absolute;
top:20px;
left:20px;
}
.colour-picker {
display:none;
}
<body>
<div id="clockOuterCircle" class="circle">
<div id="clockStyleCircle" class="circle"></div>
</div>
<!-- another circle -->
<div id="another-circle" class="circle"></div>
<!-- ... -->
</body>
What you had was pretty close. I just separated the color picking code into a new function. Try this..
var reed = document.getElementById("clockOuterCircle");
var reed1 = document.getElementById("clockStyleCircle");
reed.addEventListener('click', deed)
reed1.addEventListener('click', deed)
async function deed() {
var color = await getUserColor();
this.style.backgroundColor = color;
document.getElementById("demo").innerHTML = color;
}
function getUserColor() {
return new Promise(done => {
var color_picker = document.createElement('input');
color_picker.setAttribute('type', 'color');
color_picker.style.opacity = 0;
document.body.appendChild(color_picker);
color_picker.addEventListener('change', function() {
var color = this.value;
this.remove();
done(color);
});
color_picker.click();
});
}
#clockStyleCircle {
position: absolute;
width: 16vw;
height: 16vw;
text-align: center;
padding: 0%;
top: 28.5%;
left: 28.5%;
border-radius: 50%;
border: 3px solid black;
background-color: rgb(255, 233, 35);
}
#clockOuterCircle {
display: flex;
justify-content: center;
align-items: center;
width: 42vw;
height: 42vw;
margin: auto;
border-radius: 50%;
border: 4px solid rgb(255, 62, 62);
background-color: rgb(253, 133, 133);
user-select: none;
}
<div id="clockStyleCircle"></div>
<div id="clockOuterCircle"></div>
<div id="demo"></div>
Your example defines the background-color using two different methods class and style, which may produce unexpected results.
Here is an example using the most basic form of JavaScript, that does not set the background-color via class.
When you click an area, it will change to the selected color and when you select a color, the last area clicked, will change to the same color.
To keep it brief and simple, the example does not check, if the elements it operates on, are well defined.
// The last area clicked
var div_last_clicked = 0;
// Called when the document has finished loading
function myonload() {
// Detect when an area is clicked
let elems = document.querySelectorAll('.mydiv');
for(let i = 0; i < elems.length; ++i) {
let elem = elems[i];
elem.addEventListener('click', mydiv_clicked);
}
// Detect when the color changes
let ob = document.querySelector('.mycolor');
ob.addEventListener('change', mycolor_changed);
}
// Called when an area is clicked
function mydiv_clicked(e) {
let ob = document.querySelector('.mycolor');
div_last_clicked = e.target;
div_last_clicked.style = "background-color:" + ob.value;
}
// Called when the color changes
function mycolor_changed(e) {
if(div_last_clicked)
div_last_clicked.style = "background-color:" + e.target.value;
}
.mydiv {
height:30px;
width: 200px;
border:1px solid black;
margin-bottom:2px;
}
<body onload="myonload()">
<input class="mycolor" type="color" value="#ff0000" />
<p>When you click an area below, it will change to the selected color.</p>
<p>When you change the color, the last area clicked will also change
to the same color.</p>
<div class="mydiv">First area</div>
<div class="mydiv">Second area</div>
<div class="mydiv">Third area</div>
</body>
Based on your additional info, if you want to have a single colour input on the page, you can add a data-id attribute to each of your circles, and use that as a reference for which element the colour input should update when changed:
var restyleBG = document.querySelectorAll(".restyleBackground")
var colorPicker = document.getElementById("colorClock");
var selected;
restyleBG.forEach((restyle) => {
restyle.addEventListener('click', changeBGcolor, false)
})
function changeBGcolor(event) {
event.stopPropagation()
selected = document.querySelector(`[data-id="${event.srcElement.dataset.id}"]`);
colorPicker.click();
}
colorPicker.addEventListener('input', (event) => {
selected.style.backgroundColor = colorPicker.value;
})
#clockStyleCircle {
position: absolute;
width: 16vw;
height: 16vw;
text-align: center;
padding: 0%;
top: 28.5%;
left: 28.5%;
border-radius: 50%;
border: 3px solid black;
background-color: rgb(255, 233, 35);
z-index: 1;
}
#clockOuterCircle {
display: flex;
justify-content: center;
align-items: center;
width: 42vw;
height: 42vw;
margin: auto;
border-radius: 50%;
border: 4px solid rgb(255, 62, 62);
background-color: rgb(253, 133, 133);
user-select: none;
}
#another-circle {
width: 50px;
height: 50px;
border-radius: 50%;
border: 4px green solid;
background-color: blue;
position: absolute;
top: 20px;
left: 20px;
}
#colorClock {
display: none;
}
<body>
<input type="color" name="colorClock" id="colorClock">
<div id="clockOuterCircle" class="restyleBackground" data-id="1">
<div id="clockStyleCircle" class="restyleBackground" data-id="2"></div>
</div>
<!-- another circle -->
<div id="another-circle" class="restyleBackground" data-id="3"></div>
<!-- ... -->
</body>

strange behavior of the offsetheight element

I have a part of the page that looks like this (i use pug view engine)
<div class="product">
<div class="name">#{product.product_name}</div>
<div class="category">#{product.category}</div>
<div class="description">
| #{product.product_description}
</div>
<div class="amount">#{product.product_amount}</div>
<div class="count-stock">#{product.product_count_stock}</div>
<div>
<a href="/editProductProperties?productId=#{product.product_id}">
<img src="images/pencil-white.svg" class="pencil-icon icon" data-product_id="#{product.product_id}"/>
</a>
<img src="images/trash.svg" class="trash-icon icon"/>
</div>
</div>
And style for this part:
.product {
display: grid;
grid-template-columns: [name] 207px [category] 150px [description] 1fr [amount] 100px [count-stock] 100px [action] 54px;
}
.product > div:not(:last-child) {
padding-right: 5px;
}
.product {
padding: 20px 0;
border-top: 1px solid #f2f2f2;
position: relative;
}
.product::after {
width: 0;
height: 0;
border-top: 100px solid red;
border-right: 100px solid transparent;
position: absolute;
}
.product .description {
position: relative;
}
.product .description .showButton {
position: absolute;
top: 10px;
right: 0;
text-decoration: underline;
cursor: pointer;
}
.product:last-child {
border-bottom: 1px solid #f2f2f2;
}
.product:hover {
background: #555454;
}
And js:
window.onresize = addShowButton;
function addShowButton() {
productDescriptionList.forEach(el => {
if(el.offsetHeight > 40) {
if(el.children.length === 0) {
let showButton = document.createElement('span');
showButton.className = 'showButton';
showButton.innerText = 'Показать полностью';
el.append(showButton);
}
el.style["max-height"] = '40px';
el.style.overflow = 'hidden';
} else if(el.offsetHeight <= 40) {
el.style["max-height"] = 'initial';
el.style.overflow = 'visible';
if(el.children.length > 0) {
el.children[0].remove();
}
}
});
}
addShowButton();
The problem is that el.offsetHeight takes either the old element height values or the values before all styles are applied.
That is, I look at the height value of the element in the inspector and it is 80, and offsetHeight = 36 in console and I can not understand why this is

Continue propagation

I have some nested elements on my page with a same handler on them which should be called only for an event target without affecting elements higher in DOM tree. To achieve this behavior I used stopPropagation method and it was ok. Then I had to add some handlers for body and other elements outside the nested divs which should be called in any case. Of course stopPropagation isn't an option now but how can I make it work?
Here is a sample:
html:
<div id="container">
<div id="nested1" class="nested">
<div id="nested2" class="nested">
<div id="nested3" class="nested">
<div id="no-handler"></div>
</div>
</div>
</div>
</div>
css:
#container {
display: block;
width: 398px;
height: 398px;
padding: 30px;
border: solid 1px #888;
}
#nested1 {
width: 336px;
height: 336px;
padding: 30px;
}
#nested2 {
width: 274px;
height: 274px;
padding: 30px;
}
#nested3 {
width: 212px;
height: 212px;
padding: 30px;
}
#no-handler {
width: 150px;
height: 150px;
padding: 30px;
border: solid 1px #888;
}
.nested {
border: solid 1px #888;
}
.nested-clicked {
background-color: red;
}
.outer-clicked {
background-color: green;
}
js:
var container = document.getElementById("container");
var nested = document.getElementsByClassName("nested");
function outerHandler(e) {
this.classList.add("outer-clicked");
}
function nestedHandler(e) {
e.stopPropagation();
this.classList.add("nested-clicked");
}
container.addEventListener("click", outerHandler, false);
document.body.addEventListener("click", outerHandler, false);
for (var i = 0; i < nested.length; i++) {
nested[i].addEventListener("click", nestedHandler, false);
}
jsfiddle link:
http://jsfiddle.net/6kgnu7fr/
clicking on .nested should add red background color to clicked element and add green color to outer body and #container
UPD:
http://jsfiddle.net/6kgnu7fr/2/
clicking on #no-event or any other element inside .nested should also call nestedHandler for this .nested element.
You can check for the event's target in your nestedHandler instead of stopping the propagation. Change the class only if the target is this so that the effet will only be applied for the div on which the event occurred:
function nestedHandler(e) {
if (e.target === this) {
this.classList.add("nested-clicked");
}
}
Edit
Following your edit, this is harder. Way to do it is to find e.target's first ancestor with the "nested" class, then doing the comparison with it instead of target:
function findAncestorWithClass(dom, targetClass){
if(!dom){
return; // (undefined)
}
if(dom.classList.contains(targetClass)){
return dom;
}
// terminal recursion
return findAncestorWithClass(dom.parentNode, targetClass);
}
This is naïve shot. You may want to look for a way to make it more efficient, e.g. by avoiding to look for the first ancestor on each .nested div.
See the working snipped below.
var container = document.getElementById("container");
var nested = document.getElementsByClassName("nested");
function outerHandler(e) {
this.classList.add("outer-clicked");
}
function findAncestorWithClass(dom, targetClass){
if(!dom){
return; // (undefined)
}
if(dom.classList.contains(targetClass)){
return dom;
}
// terminal recursion
return findAncestorWithClass(dom.parentNode, targetClass);
}
function nestedHandler(e) {
var nestedParent = findAncestorWithClass(e.target, "nested");
if (this === nestedParent) {
nestedParent.classList.add("nested-clicked");
}
}
container.addEventListener("click", outerHandler, false);
document.body.addEventListener("click", outerHandler, false);
for (var i = 0; i < nested.length; i++) {
nested[i].addEventListener("click", nestedHandler, false);
}
#container {
display: block;
width: 398px;
height: 398px;
padding: 30px;
border: solid 1px #888;
}
#nested1 {
width: 336px;
height: 336px;
padding: 30px;
}
#nested2 {
width: 274px;
height: 274px;
padding: 30px;
}
#nested3 {
width: 212px;
height: 212px;
padding: 30px;
}
#sub-nested {
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.4);
}
.nested {
border: solid 1px #888;
}
.nested-clicked {
background-color: red;
}
.outer-clicked {
background-color: green;
}
<div id="container">
<div id="nested1" class="nested">
<div id="nested2" class="nested">
<div id="nested3" class="nested">
<div id="sub-nested"></div>
</div>
</div>
</div>
</div>

Categories

Resources