I have a little mouse speed detector (which is far from perfect) which gives me the current mouse speed every 100ms in the variable window.mouseSpeed.t.
I only implemented it because I want to have a funny animation on the bottom edge of the screen with a bar that grows with higher speeds and shrinks with lower speeds.
I want it to be animated with Element.animate().
The only problem is: How can I change the Animation's end keyframe (I only give an end frame so the browser assumes the current status as the first frame) while the animation is running?
I want to achieve that the bar smoothly changes its length.
// The code I want to have animated is below this function.
// Mouse speed tracker (I hope this isn't too horrible code):
document.addEventListener('DOMContentLoaded', mausgeschwindigkeitVerfolgen, {once:true});
function mausgeschwindigkeitVerfolgen() { // "Mausgeschwindigkeit verfolgen" means "track mouse speed" in German
var speedX = NaN;
var speedY = NaN;
var posX = NaN;
var posY = NaN;
var speed = NaN;
document.addEventListener("mousemove", function(ev){
speedX += Math.abs(ev.movementX);
speedY += Math.abs(ev.movementY);
speed = 10*Math.sqrt(ev.movementX**2+ev.movementY**2);
window.mousePosition = {x:posX = ev.clientX,y:posY = ev.clientY};
}, false);
setInterval(function(){
[window.mouseSpeed, window.mousePosition] = [{x:speedX,y:speedY,t:speed}, {x:posX,y:posY}]; // Werte in window.mouseSpeed und window.mouseDistance speichern
speed = totalX = totalY = 0;
}, 100);
window.mausgeschwindigkeitVerfolgen = () => {return {speed:window.mouseSpeed, pos:window.mousePosition};};
return {speed:window.mouseSpeed, pos:window.mousePosition};
}
// --- This is the code I want to have animated: ---
setInterval(() => {
document.querySelector('div#mouseSpeedIndicator').style.width = window.mouseSpeed.t+'px';
//document.querySelector('div#mouseSpeedIndicator').animate({width:'0px'}, {duration:1000,iterations:1}); // This just keeps the bar at width 0, I want it to slowly change to any newly set width
}, 100);
div#mouseSpeedIndicator {
position: fixed;
bottom: 0px;
left: 0px;
height: 33px;
background-color: green;
max-width: 100vh;
border: 0px solid green;
border-top-right-radius: 10px;
}
<!-- What I currently have -->
<div id="mouseSpeedIndicator"></div>
First, something as simple as one additional line of the transition CSS property like e.g. ...
transition: width 1s ease-out;
... already does the job; no need for more JavaScript based computation and DOM manipulation.
But of cause the OP's script could be dramatically simplified with or even without the support of an external helper method like throttle (lodash _.throttle or underscorejs _.throttle) where the latter would create a delayed executed version of the passed function which for the OP's example-script is the 'mousemove'-handler.
This handler before being throttled (or even not throttled) could be created as a bound version of the function which actually computes the speed value and updates the indicator-node's appearance.
function handleMouseSpeedIndicatorUpdateFromBoundData(evt) {
const { movementX, movementY } = evt;
const { rootNode, timeoutId } = this;
// prevent indicator nullification at time.
clearTimeout(timeoutId);
// compute `speed`.
const speed = 10 * Math.sqrt(movementX**2 + movementY**2);
// update indicator appearance.
rootNode.style.width = `${ speed }px`;
// trigger delayed indicator nullification.
this.timeoutId = setTimeout(() => rootNode.style.width = 0, 110);
}
function initialzeMouseSpeedIndicator() {
document
.addEventListener(
'mousemove',
// create throttled version of the just created bound handler.
_.throttle(
// create handler function with bound contextual data.
handleMouseSpeedIndicatorUpdateFromBoundData.bind({
rootNode: document.querySelector('#mouseSpeedIndicator'),
timeoutId: null,
}), 100
),
false
);
}
// - no need for `'DOMContentLoaded'`
// in order to initialize the indicator.
initialzeMouseSpeedIndicator();
div#mouseSpeedIndicator {
position: fixed;
top: 0px;
left: 0px;
height: 33px;
background-color: green;
max-width: 100vh;
border: 0px solid green;
border-bottom-right-radius: 10px;
/* proposed change(s) */
transition: width 1s ease-out;
/* transition: width .5s ease-in; */
/* transition: width .5s ease-in-out; */
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
<div id="mouseSpeedIndicator"></div>
Related
So I have this UL with content that changes on the fly. I figured out a fairly neat solution to smoothly increasing the height as new items get added, but I'm stuck on the reverse.
Through CSS, initial height is set to 0, with overflow: hidden. A mutation observer watches the element, and as content changes it modifies the .style.height to equal the .scrollHeight, which combined with a transition in the CSS makes a nice smooth expansion to the element. Unfortunately it doesn't work in reverse, because of course .scrollHeight refers to the fully sized element. Is there some way to get the calculated height of fit-content without actually changing .style.height?
Vanilla solutions only please (no jquery, etc). This is for an offline project I'm trying to keep to one file.
var observer = new MutationObserver(() => {
observer.target.style.height = observer.target.scrollHeight + 'px'
})
observer.target = document.querySelector('#animated')
observer.observe(observer.target, { childList: true })
var lines = 0
function demo(inc) {
lines += inc
observer.target.innerHTML = '<li>Demonstration</li>'.repeat(lines)
}
#animated {
height: 0;
overflow: hidden;
border: 1px solid black;
transition: 1s;
}
<button onclick="demo(1)">Demo Expand</button>
<button onclick="demo(-1)">Demo Contract</button>
<ul id="animated"></ul>
Using the total height of children
[...observer.target.children].reduce((a, i) => a + i.scrollHeight, 0) + 'px'
var observer = new MutationObserver(() => {
observer.target.style.height = [...observer.target.children].reduce((a, i) => a + i.scrollHeight, 0) + 'px'
})
observer.target = document.querySelector('#animated')
observer.observe(observer.target, {
childList: true
})
var lines = 0
function demo(inc) {
lines += inc
observer.target.innerHTML = '<li>Demonstration</li>'.repeat(lines)
}
#animated {
height: 0;
overflow: hidden;
border: 1px solid black;
transition: 1s;
}
<button onclick="demo(1)">Demo Expand</button>
<button onclick="demo(-1)">Demo Contract</button>
<ul id="animated"></ul>
I want to link the background color of the body element to the scroll position such that when the page is scrolled all the way to the top its color 1, but then but then when its scrolled past screen.height, its a completely different color, but I want it to be interpolated such that when it is half-way scrolled, the color is only half-way transitioned. So far, I have it linked to
$(window).scrollTop() > screen.height
and
$(window).scrollTop() < screen.height
to add and remove a class that changes background-color but I want it to be dependent on scroll position not just to trigger the event, but rather smoothly animate it so fast scrolling transitions quickly, slow scrolling transitions it slowly.
One of possible solutions is to bind a rgb color to current height, count the step and set new rgb color depending on current position of scrolling. Here I've created the simplest case - black and white transition:
const step = 255 / $('#wrapper').height();
const multiplier = Math.round(
$('#wrapper').height() /
$('#wrapper').parent().height()
);
$('body').scroll(() => {
const currentStyle = $('body').css('backgroundColor');
const rgbValues = currentStyle.substring(
currentStyle.lastIndexOf("(") + 1,
currentStyle.lastIndexOf(")")
);
const scrolled = $('body').scrollTop();
const newValue = step * scrolled * multiplier;
$('#wrapper').css('background-color', `rgb(${newValue}, ${newValue}, ${newValue})`);
});
html,
body {
padding: 0;
margin: 0;
width: 100%;
height: 100%;
overflow-x: hidden;
background-color: rgb(0, 0, 0);
}
#wrapper {
height: 200%;
width: 100%;
overflow: hidden;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<section id="wrapper"></section>
And here is another one example with transition from yellow to blue:
const step = 255 / $('#wrapper').height();
const multiplier = Math.round(
$('#wrapper').height() /
$('#wrapper').parent().height()
);
$('body').scroll(() => {
const currentStyle = $('body').css('backgroundColor');
const rgbValues = currentStyle.substring(
currentStyle.lastIndexOf("(") + 1,
currentStyle.lastIndexOf(")")
);
const scrolled = $('body').scrollTop();
const newValue = step * scrolled * multiplier;
$('#wrapper').css('background-color', `rgb(${255 - newValue}, ${255 - newValue}, ${newValue})`);
});
html,
body {
padding: 0;
margin: 0;
width: 100%;
height: 100%;
overflow-x: hidden;
background-color: rgb(255, 255, 0);
}
#wrapper {
height: 200%;
width: 100%;
overflow: hidden;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<section id="wrapper"></section>
var randomHex = function () {
return (parseInt(Math.random()*16)).toString(16) || '0';
};
var randomColor = function () {
return '#'+randomHex()+randomHex()+randomHex();
};
var randomGradient = function () {
$('.longContent').css('background', 'linear-gradient(0.5turn, #222, '+randomColor()+','+randomColor()+')');
};
$(window).on('load', randomGradient);
body {
margin: 0;
}
.longContent {
height: 400vh;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tween.js/17.2.0/Tween.min.js"></script>
<div class="longContent"></div>
A much, much easier way to accomplish what you're looking to do is by simply using a gradient as the background.
There is absolutely zero need for any JS here, which will only slow down the page.
body {
height: 600vh;
background: linear-gradient(#2E0854, #EE3B3B)
}
Is there a particular reason you want to do this with JS?
I'm pretty new to javascript and I have a question. I'm currently trying to make a loading bar change color and reset after it has reached 100%, but the main issue is, or at least I think my if statement is invalid. Anyway to test for color and width of an element? Thanks.
function upg_01() {
if (cash >= priceUpg_01) {
var width = 0;
width = width + 20;
a.style.width = width + '%';
priceUpg_01 = priceUpg_01 * 10;
multiplier_01 = multiplier_01 * 3;
}
if (document.getElementById('upg_01c').style.color === "green" &&
document.getElementById('upg_01c') === 100 + '%') {
document.getElementById('upg_01c').style.color = "yellow";
document.getElementById('upg_01c').style.width = 0 + '%';
}
const timer = setInterval( () => {
// select the progress bar element
const progressBar = document.getElementById('progress-bar');
// get the innerHTML of the progess bar
// doubling as our progress state/value
let progress = parseInt( progressBar.innerHTML );
// arbitrary addition/growth of the progress
progress += 3;
// check if progress has met or exceeded 100%
if( progress >= 100 ){
// this is just for the setInterval to stop it
// from continually executing
clearInterval( timer );
// add a new class to the progress bar (keeping
// the CSS separate from the JavaScript -- there's
// a lot of opinions on this, do what you feel
// comfortable with
progressBar.className = 'yellow';
}
// Update the progress value inside the progress
// bar
progressBar.innerHTML = progress;
// Update the width of the progress bar by setting
// the css style value
progressBar.style.width = progress + '%';
// timed loop that will trigger every 100 miliseconds
}, 100 );
#progress-bar{
color: transparent;
font-size: 0px;
width: 0%; height: 3px;
overflow: hidden;
background-color: green;
transition: all 0.5s;
}
#progress-bar.yellow{ background-color: yellow; }
#loader-wrapper{
padding: 5%;
width: 90%;
border: solid 1px black;
border-radius: 5px;
}
<div id="loader-wrapper">
<div id="progress-bar">0</div>
</div>
I tried the CSS route but all new to me and learning still but cannot seem to work it out. My JS below switches the background position to show a new image in the sprite every 1 second but wondering if anyone knew how I can kind of give it a small scale effects so when it changes grows a little then back to normal size before change to the next background position?
JS:
// Avatar animations
var avatarInterval;
function startAvatarAnimation() {
var i = 0;
var avatarSpeed = 500;
var avatarCount= 11;
var avatarHeight = 250;
var avatarTotalHeight = 2750;
avatarInterval = setInterval(function(){
i++;
if(i > 11){
i = 0;
}
$(".avatars").css({'background-position' : '0 -' + (i*avatarHeight) + 'px' });
$(".avatars").toggleClass('scaleIn', 'scaleOut');
}, avatarSpeed);
return false;
}
function stopAvatarAnimation(){
clearInterval(avatarInterval);
$(".avatars").css({'background-position' : '0 0' });
return false;
}
JS below switches the background position to show a new image in the
sprite every 1 second but wondering if anyone knew how i can kind of
give it a small scale effects so when it changes grows a little then
back to normal size before change to the next background position?
Try utilizing transition at css , setting duration to half of avatarSpeed or half of total duration of background-position effect ; setting transitionend event at .one() to prevent recursive call to transitionend handler , .removeClass() , .addClass() to toggle scale effect defined at css
css
.avatars {
transition: transform 500ms ease-in-out;
width: 100px;
height: 100px;
background-image: url(/path/to/background-image);
}
.avatars.scaleIn {
transform: scale(1.1, 1.1);
}
.avatars.scaleOut {
transform: scale(1.0, 1.0);
}
js
// Avatar animations
var avatarInterval;
function startAvatarAnimation() {
var i = 0;
var avatarSpeed = 500;
var avatarCount= 11;
var avatarHeight = 250;
var avatarTotalHeight = 2750;
avatarInterval = setInterval(function(){
i++;
if(i > 11){
i = 0;
}
$(".avatars").css({'background-position' : '0 -' + (i*avatarHeight) + 'px' })
.removeClass("scaleOut").addClass("scaleIn")
.one("transitionend", function() {
$(this).removeClass("scaleIn").addClass("scaleOut");
});
}, avatarSpeed);
return false;
}
$(".avatars").on("click", function() {
$(this).removeClass("scaleOut").addClass("scaleIn")
.one("transitionend", function() {
$(this).removeClass("scaleIn").addClass("scaleOut");
})
})
.avatars {
transition: transform 500ms ease-in-out;
width: 100px;
height: 100px;
background-image: url(http://lorempixel.com/100/100/nature);
}
.avatars.scaleIn {
transform: scale(1.1, 1.1);
}
.avatars.scaleOut {
transform: scale(1.0, 1.0);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js">
</script>
<div class="avatars"></div>
I have an array of divs that I create programmatically, like so:
// Create grid of pads dynamically, 16x16 in size
var padIdCount = 0;
for (var i = 0; i < 16; i++) {
for (var j = 0; j < 16; j++) {
var newPad = document.createElement("div");
WinJS.Utilities.addClass(newPad, "pad");
WinJS.Utilities.addClass(newPad, "row" + i);
WinJS.Utilities.addClass(newPad, "col" + j);
newPad.id = "pad" + padIdCount;
document.getElementById("padContainer").appendChild(newPad);
padIdCount++;
}
// Add a single line break after 16 pads
var newLineBreak = document.createElement("br");
document.getElementById("padContainer").appendChild(newLineBreak);
}
Pads are dark grey, but if a user taps on a pad it becomes an "active pad"
// Assign handler to pads, to toggle activation on click
var pads = document.querySelectorAll(".pad");
for (var padCount = 0; padCount < pads.length; padCount++) {
pads[padCount].addEventListener("click", togglePadActivation, false);
}
function togglePadActivation(e) {
// Get id of pad in question
var padId = e.target.id;
// Change pad from active to inactive and vice versa
var clickedPad = document.getElementById(padId);
WinJS.Utilities.toggleClass(clickedPad, "activePad");
}
Styles
/* dark grey */
.pad {
height: 40px;
width: 40px;
margin: 1px;
padding: 0px;
background-color: #363636;
display: inline-table;
}
/* dark orange */
.activePad {
background-color: orangered;
opacity: 0.6;
}
.playingPad {
background-color: orangered;
opacity: 0.6;
/* workaround for pulse bug */
animation: pulselightonanimation 3.2s ease-out;
animation-iteration-count: infinite;
}
#keyframes pulselightonanimation {
0% {opacity: 1.0;}
50% {opacity: 0.8;}
100% {opacity: 0.6;}
}
Now that setup is complete, and event handlers are hooked up, the user can click a button that starts looping through all active pads (row 1 first, simultaneously, then row 2 simultaneously, and so on). To achieve this, I implemented a setTimeout.
// Run this method every 200ms
function playActiveNotes(rowCount) {
timer = setTimeout(function () {
// Reset after the last row, because we are looping indefinitely
if (rowCount === 16) {
rowCount = 0;
}
// Select all pads from the same row at once
var currentRow = ".row" + rowCount;
var currentRowPads = document.querySelectorAll(currentRow);
// Cycle through each pad, animate and play it if active
for (var padCount = 0; padCount < currentRowPads.length; padCount++) {
// If the pad is active play the note
if (WinJS.Utilities.hasClass(currentRowPads[padCount], "activePad")) {
// Show the pulse animation
WinJS.Utilities.addClass(currentRowPads[padCount], "playingPad");
// TODO: Play short mp3 file here
// Remove the animation class. This is where it fails
//currentRowPads[padCount].addEventListener("animationend", callback(currentRowPads[padCount]), false);
//currentRowPads[padCount].addEventListener("transitionend", callback(currentRowPads[padCount]), false);
// If I use a nested setTimeout, this removeClass code never gets called. If I comment it out, I don't see the pulse, i.e. add + remove happens too quickly
//setTimeout(function () {
if (padCount < 16)
WinJS.Utilities.removeClass(currentRowPads[padCount], "playingPad");
//}, 1000);
}
}
// Go to the next row
rowCount++;
// Then call self to setup a recursive loop.
playActiveNotes(rowCount);
}, 200);
}
function callback(element) {
element.classList.remove('playingPad');
}
When the active pad is being played (I play a short mp3 file), I want it to pulse, i.e. become bright orange quickly (but not instantaneously), then fade back to the original dark orange. How can I achieve this? I tried using a playing pad class with css animation, and removing the class on transitionend/animation end, but that didn't work.
Use of WinJS is allowed in addition to Javascript, but no JQuery.
I think if somehow you can get a CSS animation going, that that would still be the best solution.
However, this is an alternative solution, using CSS transitions:
/* dark grey */
.pad {
height: 40px;
width: 40px;
margin: 1px;
padding: 0px;
background-color: #363636;
display: inline-table;
}
/* dark orange */
.activePad {
background-color: #B2371B;
transition: background-color .5s;
-webkit-transition: background-color .5s;
}
/* bright orange */
.brightPad {
background-color: #FFFFFF;
transition: background-color .1s;
-webkit-transition: background-color .1s;
}
This should make it so that if you add the .brightPad class to a pad, that it goes quickly to bright orange (in .1s). As soon as you remove that class and set .activePad again, it should go slowly back to dark orange (.5s). You will have to set the .brightPad right on touch, and then remove it again using a timeout of 100ms or more.
Here is a quick JSFiddle: http://jsfiddle.net/R5VjH/1/
(sorry, used white instead of picking a nice bright orange)