I'm using fetch API to access a JSON-object.
My questions is how can i implement a css loader before the actual data is displayed. And how do I know that the data has been displayed.
This is what I've tried:
var startSevenDaysInterval = (new Date).getTime();
var endSevenDaysInterval = startSevenDaysInterval - 604800000;
function fetchData2() {
fetch('https://vannovervakning.com/api/v1/measurements/3/' + endSevenDaysInterval + '/' + startSevenDaysInterval + '/')
.then(function (response) {
if(response === 404){
document.getElementById("loader").style.display = 'none';
}
return response.json();
})
.then(function (data) {
document.getElementById("loader").style.display = 'none';
printTemperature(data);
});
}
function printTemperature(data) {
var html = "<h5>";
html += data.data["TEMPERATURE"][0].value;
html += "</h5>";
document.getElementById('temp1').innerHTML = html;
}
setInterval(function () {
fetchData2();
}, 1000);
My loader looks like this:
.loader {
border: 4px solid #f3f3f3; /* Light grey */
border-top: 4px solid #e0e0e0;
border-radius: 30px;
width: 30px;
height: 30px;
animation: spin 1s linear infinite;
}
#keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
And my HTML looks like this:
<div class="tempVal">
<div class="loader"></div>
<div id="temp1"></div>
</div>
You are already doing that, but using getElementById for a class (it's for IDs only). When you're not sure about these things make sure you have actually got the element you want e.g. using a debugger statement or console.log(document.getElementById("loader")).
Get a class with querySelector which can handle all css selectors:
document.querySelector('.loader').style.display = 'none'
Note if you have multiple classes with that name it will get the first one. You don't need to repeat it in both .then's.
Related
I'm relatively new to this and trying to create a pop-up. I'm not sure how to get the pop-up to be in the middle of the page and still populate the box correctly, since it ends up being inside the table. I tried setting a position in the CSS but that didn't work. Maybe I did it wrong?
PHP
foreach ($updateInfo['updates'] as $update) {
echo "<table><tr><td>";
if (isset($update['details']['newsDetails']['fldDatePosted'])) {
echo '<a class="news_popper">'.$update['details']['newsDetails']['fldDatePosted'].'</a><div class="news_pop"><p>'.$update['details']['newsDetails']['fldBody'].'</p></div>';
}
echo "</td></tr></table>";
}
CSS
.news_pop {
display:none;
position: absolute;
z-index: 99999;
padding: 10px;
background: #ffffff;
border: 1px solid #A2ADBC;
}
JS
$(function() {
$('a.news_popper').click(function() {
$(this).next(".news_pop").toggle();
});
});
I would suggest coding a popup in a more object oriented approach, that way you can call it whenever you need it throughout the page, and you can have multiple instances if needed. What I would do is create a constructor for the popup and a div that manages the pop ups.
First the pop up manager:
const popUpManager = (function popUpManager() {
const $popUpManager = document.createElement('div');
$popUpManager.className = "pop-up_manager";
document.body.append($popUpManager);
return {
createPopUp: () => {
const popup = new Popup();
const $popup = popup.getPopup();
$popUpManager.append($popup);
return popup;
}
}
})();
We create a div called pop-up_manager that will house your popup and allow you to place it where ever you need throughout the page.
Then we need to create the blueprint for what a popup is:
class Popup{
constructor() {
this.$popup = document.createElement('div');
this.$popup.className = 'pop-up';
this.$popup.innerHTML = '<div class="exit-button">X</div><div class="body"></div>';
this.$exitButton = this.$popup.querySelector('.exit-button');
this.$body = this.$popup.querySelector('.body');
this.setUpListeners();
}
getPopup() {
return this.$popup;
}
setContent($content) {
if (typeof $content === 'string') {
this.$body.innerHTML = $content;
} else {
this.$body.appendChild($content);
}
}
Every time we call new Popup(); we will generate a div that floats over the page that we can set to house anything we want by passing content to the setContent() method. This will create what is in the pop up.
Finally, the styling can be configured to whatever you want in the css:
.pop-up {
position: fixed;
height: 300px;
width: 300px;
background-color: grey;
/* Positioning */
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.body {
height: 100%;
width: 100%;
}
.exit-button {
cursor: pointer;
}
To call the popup from anywhere in you JS all you have to do is:
$('a.news_popper').click(function() {
const popup = popUpManager.createPopUp();
popup.setContent('<h1>INSERT CONTENT TO SHOW USER HERE</h1>');
});
Here is a codepen: CodePen Link
I just want to ask. I want to make the product image thumbnail in shopify disappear when I scrolled down to bottom of the page, and I want a bit of transition with it.. I really can't figure out how to do this..
Here's my code..
https://jsfiddle.net/vsLdz4qb/1/
function myFunction(screenWidth) {
if (screenWidth.matches) { // If media query matches
window.onscroll = function(ev) {
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
document.getElementByClass("product-single__thumbnails").style.transition = "0.65s";
document.getElementByClass("product-single__thumbnails").style.opacity = 0;
}
};
}
}
let screenWidth = window.matchMedia("(min-width: 750px)");
myFunction(screenWidth); // Call listener function at run time
screenWidth.addListener(myFunction)
Thank you so much in advance!
The correct document method is document.getElementsByClassName and since it returns an array you need the first element of it so change this:
document.getElementByClass("product-single__thumbnails").style.transition = "0.65s";
document.getElementByClass("product-single__thumbnails").style.opacity = 0;
to:
document.getElementsByClassName("product-single__thumbnails")[0].style.transition = "0.65s";
document.getElementsByClassName("product-single__thumbnails")[0].style.opacity = 0;
You can read more about the method here
You should use getElementsByClassName in place of getElementByClass(This is not correct function)
and this will return an array like structure so you need to pass 0 index, if only one class available on page.
or you can try querySelector(".product-single__thumbnails");
and for transition, you can define that in your .product-single__thumbnails class like: transition: opacity .65s linear; - use here which property, you want to animate.
<!-- [product-image] this is for product image scroll down disappear -->
function myFunction(screenWidth) {
if (screenWidth.matches) { // If media query matches
window.onscroll = function(ev) {
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
document.getElementsByClassName("product-single__thumbnails")[0].style.opacity = 0;
}
};
}
}
let screenWidth = window.matchMedia("(min-width: 350px)");
myFunction(screenWidth); // Call listener function at run time
screenWidth.addListener(myFunction)
body {
margin:0;
height: 1000px;
}
.product-single__thumbnails {
background-color: red;
color: white;
width: 50px;
height: 50px;
position: fixed;
transition: opacity .65s linear;
border-radius: 4px;
margin: 20px;
text-align: center;
}
<div class="product-single__thumbnails">
<p>red</p>
</div>
This is a very basic question, and for some reason I am having a hard time wrapping my brain around it. I am new and learning so bear with me please.
Here is a progress bar: https://codepen.io/cmpackagingllc/pen/ZNExoa
When the bar has loaded completely it adds the class completed as seen on js line 41.
progress.bar.classList.add('completed');
So say once completed I want to add an Alert that say's "completed". I assume this would be an easy task but because of the way the code is written with the loop seen on line 46/47
loop();
}, randomInterval);
I am unable to incorporate the Alert properly without an alert loop even when I used return false to stop the loop afterwards.
So the route I am trying to take now is to add the alert prompt to the success function found on line 21-25
function success() {
progress.width = progress.bar.offsetWidth;
progress.bar.classList.add('completed');
clearInterval(setInt);
alert("Completed!");
}
But now I am stuck trying to format it correctly so when the if is called on line 36
if (progress.width >= progress.bar.offsetWidth) {
When the if is called on line 36 I want to to jump to the success function instead. No matter how I try it the code fails to execute. How would I format this correctly so it jumps to my function instead of looping after completed?
I would greatly appreciate some assistance with this. I am trying to understand if there is a better way to add the alert. Thank you much.
I read your code with special attention because recently I have been working with some loading bars (but not animated ones).
The problem is that you are using setTimeout() and not setInterval(), so calling clearInterval() has no effect at all. And you really don't need setInterval() because you're already making recursive calls (looping by calling the same function from its body).
I've took the liberty of rewriting your code for you to analyse it. Please let me know if you have any doubts.
NOTE: It's easier in this case to use relative units for the width! So you don't have to calculate "allowance".
let progress = {
fill: document.querySelector(".progress-bar .filler"),
bar: document.querySelector(".progress-bar"),
width: 0
};
(function loop() {
setTimeout(function () {
progress.width += Math.floor(Math.random() * 50);
if (progress.width >= 100) {
progress.fill.style.width = '100%';
progress.bar.classList.add('completed');
setTimeout(function () {
alert('COMPLETED!');
}, 500);
} else {
progress.fill.style.width = `${progress.width}%`;
loop();
}
}, Math.round(Math.random() * (1400 - 500)) + 500);
})();
Like a comment said, there are several timers in your code. Also, success was never executed. Here you have a version that works.
If you are learning, try to make your code as simple as possible, use pseudocode to see in wich step there is an error and try debugging from there.
var progress = {
fill: document.querySelector(".progress-bar .filler"),
bar: document.querySelector(".progress-bar"),
width: 0 };
function setSize() {
var allowance = progress.bar.offsetWidth - progress.width;
var increment = Math.floor(Math.random() * 50 + 1);
progress.width += increment > allowance ? allowance : increment;
progress.fill.style.width = String(progress.width + "px");
}
function success() {
progress.width = progress.bar.offsetWidth;
progress.bar.classList.add('completed');
alert("Completed!");
}
(function loop() {
var randomInterval = Math.round(Math.random() * (1400 - 500)) + 500;
var setInt = setTimeout(function () {
setSize();
if (progress.width >= progress.bar.offsetWidth) {
success();
} else {
loop();
}
}, randomInterval);
})();
.progress-bar {
height: 10px;
width: 350px;
border-radius: 5px;
overflow: hidden;
background-color: #D2DCE5;
}
.progress-bar.completed .filler {
background: #0BD175;
}
.progress-bar.completed .filler:before {
opacity: 0;
}
.progress-bar .filler {
display: block;
height: 10px;
width: 0;
background: #00AEEF;
overflow: hidden;
transition: all 0.5s cubic-bezier(0.25, 0.8, 0.25, 1);
}
.progress-bar .filler:before {
content: '';
display: block;
background: repeating-linear-gradient(-45deg, #00AEEF, #00AEEF 10px, #23c3ff 10px, #23c3ff 20px);
height: 10px;
width: 700px;
border-radius: 5px;
animation: fill 10s linear infinite;
}
#keyframes fill {
from {
transform: translatex(-350px);
}
to {
transform: translatex(20px);
}
}
<div class="progress-bar">
<span class="filler"></span>
</div>
What I'm doing and what's wrong
When I click on a button, a slider shows up. (here is an example of what it looks like, do not pay attention to this code)
The slider shows via an animation. When the animation is finished I should include an HTML page I've loaded from the server. I need to apply the HTML in the slider after the animation otherwise the animation stops (the DOM is recalculated).
My algorithm
Start the request to get the HTML to display inside the slider
Start the animation
Wait the data to be ready and the transition to be finished
Why? If I apply the HTML during the animation, it stops the animation while the new HTML is added to the DOM. So I wait for both to end before step 4.
Apply the HTML inside the slider
Here is the shortened code:
// Start loading data & animate transition
var count = 0;
var data = null;
++count;
$.get(url, function (res) {
data = res;
cbSlider();
});
// Animation starts here
++count;
$(document).on('transitionend', '#' + sliderId, function () {
$(document).off('transitionend', '#' + sliderId);
cbSlider()
});
function cbSlider() {
--count;
// This condition is only correct when both GET request and animation are finished
if (count == 0) {
// Attempt to enforce the frame to finish (doesn't work)
window.requestAnimationFrame(() => { return });
$('#' + sliderId + ' .slider-content').html(data);
}
}
The detailed issue
transitionend is called too early. It makes the last animated frame a lot too long (477.2ms) and the last frame is not rendered at transitionend event.
From the Google documentation, I can tell you that the Paint and Composite step of the Pixel Pipeline is called after the Event(transitionend):
Maybe I'm overthinking this.
How should I handle this kind of animations?
How can I wait the animation to be fully finished and rendered?
I'm not sure why transitionend is fired before the last frame has rendered, but in this (very crude) test it seems that a setTimeout does help...
The first example shows how the html calculation and injection happens too early. The second example wraps the long running method in a setTimeout and doesn't seem to trigger any interuption in the animation.
Example 1: reproduction of your problem
var ended = 0;
var cb = function() {
ended += 1;
if (ended == 2) {
$(".animated").html(createLongHTMLString());
}
}
$(".load").click(function() {
$(".animated").addClass("loading");
$(".animated").on("transitionend", cb);
setTimeout(cb, 100);
});
function createLongHTMLString() {
var str = "";
for (var i = 0; i < 100000; i += 1) {
str += "<em>Test </em>";
}
return str;
};
.animated,
.target {
width: 100px;
height: 100px;
position: absolute;
text-align: center;
line-height: 100px;
overflow: hidden;
}
.target,
.animated.loading {
transform: translateX(300%);
}
.animated {
background: green;
z-index: 1;
transition: transform .2s linear;
}
.target {
background: red;
z-index: 0;
}
.wrapper {
height: 100px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="wrapper">
<div class="animated">Loading</div>
<div class="target"></div>
</div>
<button class="load">load</button>
Example 2: in which a setTimeout seems to fix it
With a setTimeout around the html injection code.
var ended = 0;
var cb = function() {
ended += 1;
if (ended == 2) {
setTimeout(function() {
$(".animated").html(createLongHTMLString());
});
}
}
$(".load").click(function() {
$(".animated").addClass("loading");
$(".animated").on("transitionend", cb);
setTimeout(cb, 100);
});
function createLongHTMLString() {
var str = "";
for (var i = 0; i < 100000; i += 1) {
str += "<em>Test </em>";
}
return str;
};
.animated,
.target {
width: 100px;
height: 100px;
position: absolute;
text-align: center;
line-height: 100px;
overflow: hidden;
}
.target,
.animated.loading {
transform: translateX(300%);
}
.animated {
background: green;
z-index: 1;
transition: transform .2s linear;
}
.target {
background: red;
z-index: 0;
}
.wrapper {
height: 100px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="wrapper">
<div class="animated">Loading</div>
<div class="target"></div>
</div>
<button class="load">load</button>
Well, if transitions are not working for you the way you want to, you can go back a few years and use jQuery animations instead?
(function(slider){
$.get(url, function (res) {
slider.animate({
// put whatever animations you need here
left: "5%",
}, 5000, function() {
// Animation complete.
slider.find('.slider-content').html(res);
});
});
}($('#' + sliderId)));
You can also start both actions at the same time, and then add the html to the document only after the animation has finished and the request is complete, but that would require a flag.
(function(slider){
// whether the animation is finished
var finished = false;
// whether the html has been added already
var added = false;
// your html data
var html = null;
function add() {
if (finished && html && !added) {
// make sure function will only add html once
added = true;
slider.find('.slider-content').html(html);
}
}
$.get(url, function (res) {
html = res;
add();
});
slider.animate({
// put whatever animations you need here
left: "5%",
}, 5000, function() {
// Animation complete.
finished = true;
add();
});
}($('#' + sliderId)));
Is there any way to set the from or to of a webkit-keyframe with JavaScript?
A solution of sorts:
var cssAnimation = document.createElement('style');
cssAnimation.type = 'text/css';
var rules = document.createTextNode('#-webkit-keyframes slider {'+
'from { left:100px; }'+
'80% { left:150px; }'+
'90% { left:160px; }'+
'to { left:150px; }'+
'}');
cssAnimation.appendChild(rules);
document.getElementsByTagName("head")[0].appendChild(cssAnimation);
Just adds a style definition to the header. Would be much cleaner/better to define it though the DOM if possible.
Edit: Error in Chrome with old method
You can use the CSS DOM interface. For instance:
<html>
<body>
<style>
#keyframes fadeout {
from { opacity:1; }
to { opacity:0; }
}
</style>
<script text="javascript">
var stylesheet = document.styleSheets[0];
var fadeOutRule = stylesheet.cssRules[0];
alert( fadeOutRule.name ); // alerts "fadeout"
var fadeOutRule_From = fadeOutRule.cssRules[0];
var fadeOutRule_To = fadeOutRule.cssRules[1];
alert( fadeOutRule_From.keyText ); // alerts "0%" ( and not "from" as you might expect)
alert( fadeOutRule_To.keyText ); // alerts "100%"
var fadeOutRule_To_Style = fadeOutRule_To.style;
alert( fadeOutRule_To_Style.cssText ); // alerts "opacity:0;"
fadeOutRule_To_Style.setProperty('color', 'red'); // add the style color:red
fadeOutRule_To_Style.removeProperty('opacity'); // remove the style opacity
alert( fadeOutRule_To_Style.cssText ); // alerts "color:red;"
</script>
</body>
</html>
This example covers several different browsers:
var keyFramePrefixes = ["-webkit-", "-o-", "-moz-", ""];
var keyFrames = [];
var textNode = null;
for (var i in keyFramePrefixes){
keyFrames = '#'+keyFramePrefixes[i]+'keyframes cloudsMove {'+
'from {'+keyFramePrefixes[i]+'transform: translate(0px,0px);}'+
'to {'+keyFramePrefixes[i]+'transform: translate(1440px'
'px,0px);}}';
textNode = document.createTextNode(keyFrames);
document.getElementsByTagName("style")[0].appendChild(textNode);
}
The way I handle this is to not set either the from or to of the element style I am manipulating in the css file and before triggering the animation I will set the element style that it should go to with javascript. This way you are free to dynamically manage what stuff should do until we can manage this directly in js. You only need to specify one of the two. The setTimeout allows the application of the css rule to the element before the animation is triggered otherwise you would have a race condition and it wouldn't animate.
#someDiv.slideIn {
-webkit-animation: slideIn 0.5s ease;
}
#-webkit-keyframes slideIn {
0% {
left:0px;
}
100% {}
}
var someDiv = document.getElementById('someDiv');
someDiv.style.left = '-50px';
setTimeout(function(){
someDiv.addClass('slideIn');
},0);
To solve this I added a 'webkit animation name' to my CSS selector and then created separate rules for my options, in my example red and yellow colouring:
.spinner {
-webkit-animation-name: spinnerColorRed;
}
#-webkit-keyframes spinnerColorRed {
from {
background-color: Black;
}
to {
background-color: Red;
}
}
#-webkit-keyframes spinnerColorYellow {
from {
background-color: Black;
}
to {
background-color: Yellow;
}
}
Then using jQuery:
$("#link").click(function(event) {
event.preventDefault();
$(".spinner").css("-webkit-animation-name", "spinnerColorYellow");
});
Yes, there is a way to do it dynamically with JavaScript. You can use css animation directives in native javascript code with the function Element.animate(). This approach is widely supported across browser, except for IE.
document.getElementById("tunnel").animate([
// keyframes
{ transform: 'translateY(0px)' },
{ transform: 'translateY(-300px)' }
], {
// timing options
duration: 1000,
iterations: Infinity
});
This function accepts two arguments - keyframes and options. Inside options you can specify the css animation parameters. Inside keyframes you can specify one or many transitional states. In your case with from and to you need two keyframes.