This is a summarized example of another larger project I have. In this example I set a new style for my node when I click on it, once the new style is set I fire a setTimeout() to allow the style for a few seconds until it desappear. on the other hand, I have a keydown event that is supposed to cancel the Timeout when I press any key. So if the timeout is canceled with clearTimeout(), the box is supposed to stay styled, but it is not working. The clearTimeout() doesn't cancel the timeout, and doesn't prevent the style to be deleted.
Any idea about what is wrong with the clearTimeout()?
const box = document.querySelector("div")
let timeout
box.onclick = () =>{
box.classList.add("black")
}
box.ontransitionend = () =>{
timeout = setTimeout(() =>{
box.classList.remove("black")
}, 3000)
}
window.onkeydown = () =>{
clearTimeout(timeout)
}
*{
margin: 0px;
padding: 0px;
}
.box{
width: 200px;
height: 200px;
margin: auto;
border: #333 1px solid;
transition: all 1s ease-in-out 0s;
}
.black{
background-color: black;
border: #ccc 1px solid;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>test</title>
<link rel="stylesheet" href="./styles.css">
</head>
<body>
<div class="box">
</div>
<script src="./main.js"></script>
</body>
</html>
The ontransitionend event gets called for each transitioned style, in your case, that's your background-color, as well as border-top-color, border-right-color, border-bottom-color and border-left-color for the border style change due to the all in your transitional: all ... style. You could apply a check in your event handler to only apply your timeout when the transition is for CSS style background-color by using event.propertyName. This way you won't end up creating multiple timeouts and will be able to cancel it.:
const box = document.querySelector("div");
let timeout;
box.onclick = () => {
box.classList.add("black")
}
box.ontransitionend = (event) => {
if (event.propertyName === "background-color") {
timeout = setTimeout(() => {
box.classList.remove("black")
}, 3000);
}
}
window.onkeydown = () => {
clearTimeout(timeout);
}
* {
margin: 0px;
padding: 0px;
}
.box {
width: 200px;
height: 200px;
margin: auto;
border: #333 1px solid;
transition: all 1s ease-in-out 0s;
}
.black {
background-color: black;
border: #ccc 1px solid;
}
<div class="box"></div>
I've noticed that the event ontransitionend fires once for each style in the class i've set, so i'm creating more than one Timeout.
Related
I'm trying to implement an effect on overlapping images so when I click and drag a bar the other image underneath should reveal modifying the width. My code works, but it's kind of buggy. When I click on the bar I have to then release it to make it work, so it's not really a drag, and it supposed to stop moving when I release the click.
And second, the bar goes right even outside my container. How could I fix my code?
var up = $("#up");
var bar = $("#bar");
var container = $("#container");
bar.on("mousedown", function () {
container.on("mousemove", function (e) {
bar.css("left", e.clientX);
up.width(e.clientX);
});
});
$("body").on("mouseup", function () {
container.off("mousemove");
});
container.on("mouseleave", function () {
container.off("mousemove");
});
* {
margin: 0;
box-sizing: border-box;
}
img {
height: 400px;
width: 600px;
object-fit: cover;
}
#up {
width: 50%;
}
#bottom,
#up {
position: absolute;
overflow: hidden;
}
#container {
position: relative;
border: 5px solid cornflowerblue;
height: 410px;
width: 610px;
}
#bar {
position: absolute;
height: 400px;
width: 10px;
background-color: hotpink;
left: 300px;
cursor: e-resize;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="style.css" />
<title>Document</title>
</head>
<body>
<div id="container">
<div id="bottom">
<img src="https://via.placeholder.com/600x400?text=Image1" alt="image" />
</div>
<div id="up">
<img src="https://via.placeholder.com/600x400?text=Image2" alt="image" />
</div>
<div id="bar"></div>
</div>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script src="script.js"></script>
</body>
</html>
Explanation : When the bar was being slid, the images were acting as draggable elements. To stop that, I set draggable attribute to false in both image elements. Besides that, the blue color appearing was highlight because of selection of element. To prevent that, I set CSS property user-select to none for the container divs of images.
For browser compatibility, use different versions for user-select.
var up = $("#up");
var bar = $("#bar");
var container = $("#container");
bar.on("mousedown", function() {
container.on("mousemove", function(e) {
let left = e.clientX;
let containerWidth = container.width();
let barWidth = bar.width();
if((left + barWidth) > containerWidth)
left = containerWidth - barWidth;
bar.css("left", left);
up.width(left);
});
});
$("body").on("mouseup", function() {
container.off("mousemove");
});
container.on("mouseleave", function() {
container.off("mousemove");
});
* {
margin: 0;
box-sizing: border-box;
}
img {
height: 400px;
width: 600px;
object-fit: cover;
}
#up {
width: 50%;
}
#bottom, #up {
position: absolute;
overflow: hidden;
user-select: none;
}
#container {
position: relative;
border: 5px solid cornflowerblue;
height: 410px;
width: 610px;
}
#bar {
position: absolute;
height: 400px;
width: 10px;
background-color: hotpink;
left: 300px;
cursor: e-resize;
}
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<div id="container">
<div id="bottom">
<img src="https://via.placeholder.com/600x400?text=Image1" alt="image" draggable="false"/>
</div>
<div id="up">
<img src="https://via.placeholder.com/600x400?text=Image2" alt="image" draggable="false"/>
</div>
<div id="bar"></div>
</div>
My advise is to NOT unregister (remove) the mousemove event listener.
Below, I used a barActive as a "flag" to know if the mouse button is down. The flag is resetted on mouseup and mouseleave of the container.
The mousemove event is a machinegun... But is still not fast enough to "really" follow the mouse. So if a user moves the bar fast, the cursor often is off the bar (I'm sure you noticed it). So a mouseup has good chances to not fire if the mouseup event listener is on the bar. So the event listener has to be on the container.
Now... While mousemove is not fast enought to follow the mouse perfectly... It often is too fast and may interfeer with the actions you wish to do with the other events, like mousedown were you set the "flag". At the same millisecond, you can have the mousedown event and multiple mousemove events. So to "isolate" it form the mousemove events, I used a real tiny setTimeout. That is giving enought time for the flag to be set bafore any next mousemove to enact.
About making sure the bar does not go outside the container, I used a condition on e.clientX.
I also took the container's padding in account.
console.clear();
var up = $("#up");
var bar = $("#bar");
var container = $("#container");
var barActive = false;
bar.on("mousedown", function (e) {
barActive = true;
});
container.on("mousemove mouseup mouseleave", function (e) {
if (barActive && e.type === "mousemove") {
setTimeout(function () {
if (e.clientX < container.width() && e.clientX>5) {
bar.css("left", e.clientX - 8);
up.width(e.clientX - 8);
}
}, 1);
return;
}
barActive = false; // Applies only for mouseup and mouseleave
});
* {
margin: 0;
box-sizing: border-box;
}
img {
height: 400px;
width: 600px;
object-fit: cover;
user-select: none; /* Prevents the image itself to be selected */
}
#up {
width: 50%;
}
#bottom,
#up {
position: absolute;
overflow: hidden;
}
#container {
position: relative;
border: 5px solid cornflowerblue;
height: 410px;
width: 610px;
overflow: hidden; /* Will make sure the bar does not go over the border */
}
#bar {
position: absolute;
height: 400px;
width: 10px;
background-color: hotpink;
left: 300px;
cursor: e-resize;
}
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="style.css" />
<title>Document</title>
</head>
<body>
<div id="container">
<div id="bottom">
<img src="https://via.placeholder.com/600x400?text=Image1" alt="image" />
</div>
<div id="up">
<img src="https://via.placeholder.com/600x400?text=Image2" alt="image" />
</div>
<div id="bar"></div>
</div>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script src="script.js"></script>
</body>
</html>
CodePen
so here's the code I wrote, as you can see when the trigger hits the red line, the boxes start changing the color to red, but when the trigger passes the red line again, some of the boxes turn the color to red again.
function what() {
const trigger1 = document.querySelector(".trigger1").getBoundingClientRect().y;
const element = document.querySelectorAll(".ele");
if (trigger1 < 100) {
function why() {
for (let i = 0; i < element.length; i++) {
setTimeout(() => {
element[i].classList.add('newColor');
}, i * 500)
}
}
why();
} else {
element.forEach((e) => {
e.classList.remove('newColor');
})
}
}
window.addEventListener("scroll", what);
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
font-size: 62.5%;
}
h1 {
background-color: lightcoral;
text-align: center;
padding: 1rem;
font-size: 2.5rem;
}
section {
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
ul {
position: fixed;
top: 50%;
left: 20%;
transform: translate(-50%, -50%);
text-align: center;
list-style: none;
}
ul li {
margin: 1rem;
padding: 1rem;
font-size: 2rem;
background-color: rgb(134, 146, 211);
}
.newColor {
color: white;
background-color: red;
}
.line {
position: fixed;
top: 100px;
width: 100%;
height: 1px;
background-color: red;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<section>
<div class="line"></div>
<div>
<h1 class="trigger1">here's the trigger</h1>
<ul>
<li class="ele">ayyyo</li>
<li class="ele">woooo</li>
<li class="ele">yoooo</li>
<li class="ele">loool</li>
</ul>
</div>
</section>
<section>
<div>
<h1 class="trigger2">this is trigger 2</h1>
</div>
</section>
</body>
</html>
what I want to do is, when the trigger hits the red line, the boxes change the color to red.
After that when the trigger goes back (or leave the red line to bottom), the boxes change the color to the original color and never changes the color to red unless the trigger hits the red line again.
I might need to use clearTimeout thing but not sure where or how to put.
that'd be great if you let me know how I can solve this... thanks
You can use global variable as timeout handler for clearTimeout.
Try following code.
var timeOutHandler = [];
function what() {
const trigger1 = document.querySelector(".trigger1").getBoundingClientRect().y;
const element = document.querySelectorAll(".ele");
if(trigger1 < 100){
for(let i = 0; i < element.length; i++){
if ( typeof timeOutHandler[i] === 'undefined'){
timeOutHandler[i] = setTimeout(()=> {
element[i].classList.add('newColor');
}, i *500);
}
}
} else {
for (i = 0; i < timeOutHandler.length; i++ ){
clearTimeout( timeOutHandler[i] );
timeOutHandler[i] = undefined;
}
element.forEach((e) => {
e.classList.remove('newColor');
});
}
}
window.addEventListener("scroll", what);
Explanation:
According to your code, repeated setTimeout will executed everytime when user scroll.
To prevent repeated setTimeout, you can assign handler to every element, and execute setTimeout only when handler is undefined.
So, there will be only 1 setTimeout for every element when trigger hit red line for the first time.
And when trigger went back to below of red line, stop and remove all timeouts and assign undefined to all handlers.
I am trying to implement HTML5 drag & drop but the ondragstart event doesn't fire.
I am loading tabs for my page programmaticly and apply the attributes like so:
TabCell.Attributes.Add("draggable", "true");
TabCell.Attributes.Add("ondragstart", "onDragStart(event)");
My javaScript function which will not fire:
function onDragStart(e) {
alert("TESTING");
}
Has anyone tried to add the attributes in the code behind before as I am unsure if this is the issue here?
You need to set the attributes with setAttribute:
TabCell.setAttribute("draggable", "true");
TabCell.setAttribute("ondragstart", "onDragStart(event)");
Working code example below. If you start dragging the first tab onDragStart fires and shows the message 'Drag has started'.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<style>
.tab {
overflow: hidden;
border: 1px solid #ccc;
background-color: #f1f1f1;
}
.tab button {
background-color: inherit;
float: left;
border: none;
outline: none;
cursor: pointer;
padding: 14px 16px;
transition: 0.3s;
}
.tab button:hover {
background-color: #ddd;
}
</style>
</head>
<body>
<div class="tab">
<button id="TabCell1">Tab1 Header</button>
<button id="TabCell2">Tab2 Header</button>
</div>
<div id="content"></div>
<script>
window.onload = () => {
var TabCell = document.getElementById('TabCell1');
TabCell.setAttribute("draggable", "true");
TabCell.setAttribute("ondragstart", "onDragStart(event)");
var TabCell2 = document.getElementById('TabCell2');
TabCell2.setAttribute("draggable", "true");
};
function onDragStart(ev) {
var el = document.getElementById('content');
el.innerHTML = "Drag has started";
}
</script>
</body>
</html>
I've found this and tried to fix it as the logic behind it is similar to what I'm trying to achieve. I've manage to get it working with minimal editing. but it isn't working as expected.
note: I have commented out the click feature as it is working fine.
What is happening
If you click on the volumeBtn and accidentally move the cursor out of the volumeRange div height or width while sliding either left or right, the mouseup event listener doesn't get executed when you stop clicking the mouse.
Like 1, after clicking the volumeBtn you cannot drag the volumeBtn left or right once it goes outside the `volumeRange' div.
There is a flicker from the zeroth position to the desired position.
What I Want to happen
If you click on the volumeBtn then stop clicking the mouse, the mouseup event should be executed even if the cursor is no longer on the volumeRange.
If you click on the volumeBtn you should be able to drag the volumeBtn left or right even if the cursor is no longer on the volumeRange.
const volume = document.querySelector('.volume');
const volumeRange = document.querySelector('.volume-range');
const volumeBtn = document.querySelector('.volume-button');
// volumeRange.addEventListener("click", volumeClick );
// function volumeClick(event) {
// let x = event.offsetX;
// volume.style.width = (Math.floor(x) + 10) + 'px';
// }
let mouseIsDown = false;
volumeBtn.addEventListener("mouseup", up);
volumeBtn.addEventListener("mousedown", down);
volumeRange.addEventListener("mousemove", volumeSlide);
function down(){ mouseIsDown = true; }
function up(){ mouseIsDown = false; }
function volumeSlide(event) {
if (mouseIsDown) {
let x = event.offsetX;
console.log(x);
volume.style.width = Math.floor(x + 10) + 'px';
}
}
body {
background-color: #2a2a2a;
}
.volume-range {
margin-top: 80px;
height: 5px;
width: 250px;
background: #555;
border-radius: 15px;
}
.volume-range>.volume {
height: 5px;
width: 50px;
background: #2ecc71;
border: none;
border-radius: 10px;
outline: none;
position: relative;
}
.volume-range>.volume>.volume-button {
width: 20px;
height: 20px;
border-radius: 20px;
background: #FFF;
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
cursor: pointer;
outline: none;
}
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>Volume</title <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.6.3/css/all.css" integrity="sha384-UHRtZLI+pbxtHCWp1t77Bi1L4ZtiqrqD80Kn4Z8NTSRyMA2Fd33n5dQ8lWUE00s/" crossorigin="anonymous">
</head>
<style>
</style>
<body>
<div class="volume-range">
<div class="volume">
<div class="volume-button"></div>
</div>
</div>
</body>
</html>
Why it doesn't work
This is working the way you described it because mouse events will only be fired when the mouse is inside the element that have the listeners attached.
An immediate (but not very good) solution to this will be to move the listener for "mouseup" and "mousemove" from the volumeBtn/volumeRange to the window object. This is not very good because if you will later need to remove this element, you should also remove the listeners from the window object.
Better solution
It would be better to encapsulate the slider inside another element that will give it some padding, and then put the event listeners on that "container" element. It will still stop moving when you go outside the element, but at least everything is self-contained.
This is shown in the following snippet:
const volume = document.querySelector('.volume');
const volumeRange = document.querySelector('.volume-range');
const volumeContainer = document.querySelector('.volume-container');
const volumeBtn = document.querySelector('.volume-button');
// volumeRange.addEventListener("click", volumeClick );
// function volumeClick(event) {
// let x = event.offsetX;
// volume.style.width = (Math.floor(x) + 10) + 'px';
// }
let mouseIsDown = false;
volumeContainer.addEventListener("mouseup", up);
volumeBtn.addEventListener("mousedown", down);
volumeContainer.addEventListener("mousemove", volumeSlide, true);
function down(){ mouseIsDown = true; }
function up(){ mouseIsDown = false; }
const volumeRangeWidth = volumeRange.getBoundingClientRect().width; // This will be the volume limit (100%)
function volumeSlide(event) {
if (mouseIsDown) {
let x = event.offsetX;
if (event.target.className == "volume-container") {
x = Math.floor(x);
if (x < 0) x = 0; // check if it's too low
if (x > volumeRangeWidth) x = volumeRangeWidth; // check if it's too high
volume.style.width = (x+10) + 'px';
}
}
}
body {
background-color: #2a2a2a;
}
.volume-container {
padding: 40px 0px;
margin: 0px 20px;
}
.volume-range {
height: 5px;
width: 250px;
background: #555;
border-radius: 15px;
}
.volume-range>.volume {
height: 5px;
width: 50px;
background: #2ecc71;
border: none;
border-radius: 10px;
outline: none;
position: relative;
}
.volume-range>.volume>.volume-button {
width: 20px;
height: 20px;
border-radius: 20px;
background: #FFF;
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
cursor: pointer;
outline: none;
}
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>Volume</title>
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.6.3/css/all.css" integrity="sha384-UHRtZLI+pbxtHCWp1t77Bi1L4ZtiqrqD80Kn4Z8NTSRyMA2Fd33n5dQ8lWUE00s/" crossorigin="anonymous">
</head>
<style>
</style>
<body>
<div class="volume-container">
<div class="volume-range">
<div class="volume">
<div class="volume-button"></div>
</div>
</div>
</div>
</body>
</html>
Other problems
In the fiddle it is also shown how to avoid the volume to go outside the container.
var Buttons = new Array();
var AnimEnd = new Array();
var Called = new Array();
function Start () {
Buttons = document.getElementsByClassName("google_button-main");
for (var i = 0; i < Buttons.length; i++)
{
Buttons[i].onmousedown = (e) => {
var Animator = e.target.previousElementSibling;
Called[i] = false;
AnimEnd[i] = false;
Animator.setAttribute("Animated", "true");
Animator.ontransitionend = (f) => {
if (Called[i] == false)
{
if (AnimEnd[i] == true)
{
Animator.setAttribute("Animated", "false");
}
}
Called[i] = true;
AnimEnd[i] = true;
}
}
Buttons[i].onmouseup = (e) => {
var Animator = e.target.previousElementSibling;
if (AnimEnd[i] == true)
{
Animator.setAttribute("Animated", "false");
}
AnimEnd[i] = true;
}
}
}
.google_button-main {
height: 100%;
width: 100%;
border: none;
background-color: black;
z-index: -1;
pointer-events: auto;
}
.google_button-animator[animated="false"] {
height: 0px;
width: 0px;
border-radius:50%;
background-color: rgba(255, 255, 255, 0.0);
transition: background-color 0.4s ease, height 0.4s step-end, width 0.4s step-end, border-radius 0.4s step-end;
}
.google_button-animator {
position: absolute;
pointer-events: none;
z-index: 1;
}
.google_button-animator[animated="true"] {
height: 50px;
width: 50px;
background-color: rgba(255, 255, 255, 0.5);
border-radius: 0%;
transition: height 0.4s ease, width 0.4s ease, border-radius 0.4s ease;
}
.google_button-wrapper {
position: relative;
pointer-events: none;
height: 50px;
width: 50px;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Page Title</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" media="screen" href="main.css" />
<script src="main.js"></script>
</head>
<body onload="Start();">
<div class="google_button-wrapper">
<div class="google_button-animator" animated="false">
</div>
<button class="google_button-main">
Knopf
</button>
</div>
</body>
</html>
I wanted to make a button-animation like the Google buttons.
I wanted the animation, to be played on button-down and on button-up, but only when the previous animation is finished. So I added the "AnimEnd"-array.
After that I ran into the issue, that the ontransitionend-function gets called 5 times after the transition is done, so I added the "Called"-array.
My code works, but when I click the button (In the snippet below) very fast the JavaScript-onmouseup stops working.
I tried to resolve it with the console-log, but I couldnt find the issue!