I am making a website with a splash screen that I want to make disappear after 3 seconds. I can successfully do it when I include jQuery, but this takes time to load (especially if it's not cached) and so the splash still displays for a small time.
I am also using cookies so that it will only show on the first load of the page (so it's not overly irritating).
Here's my HTML:
<div class="splash">
splash content
</div>
Here's the working jQuery (that I want to avoid):
if(document.cookie.indexOf("visited=true") === -1) {
$(".splash").delay(3000).queue(function(){
$(this).addClass("hidden").dequeue();
});
} else {
$(".splash").addClass("hidden");
}
Here's what I have come up with regarding javascript, but it doesn't work:
document.getElementsByClassName("splash").addEventListener("load",
function() {
if(document.cookie.indexOf("visited=true") === -1) {
setTimeout(function(){
this.classList.add("hidden");
}, 3000);
} else {
this.classList.add("hidden");
}
});
I don't think you want to add the function as the load event listener of the splash. You should add it to the load event of the page.
See comments inline for more details on reorganizing the code. Unfortunately, it won't work with cookies here in the Stack Overflow snippet environment.
Note that the splash is set to be hidden (via CSS) by default. This is a better practice than showing it by default and then hiding it. If, after reading the cookie, it is determined that the splash should not be shown, some users may wind up seeing the splash momentarily on their screens due to processing limitations, or worse if there is any kind of error in your code, the splash may wind up being shown and never taken away because the JS stops executing at the error.
// Get a reference to the splash dialog
var splash = document.querySelector(".splash");
// When the window is loaded....
window.addEventListener("load", function() {
// Check to see if the cookie indicates a first-time visit
if(document.cookie.indexOf("visited=true") === -1) {
// Reveal the splash (remember: splash is hidden by default by CSS)
splash.classList.remove("hidden");
// .5 seconds later, hide the splash
setTimeout(function(){
splash.classList.add("hidden");
// >> Set cookie to visited here <<
}, 500);
}
});
.splash {
height:200px;
width:200px;
background:yellow;
}
.hidden {
display:none;
}
<div class="splash hidden">S P L A S H !</div>
document.getElementsByClassName("splash").addEventListener("load", //not possible as ByClassName returns a collection not an element
function() {
if(document.cookie.indexOf("visited=true") === -1) {//why not simply !...
setTimeout(function(){
this.classList.add("hidden");//this is window as setTimeout is a window function...
}, 3000);
} else {
this.classList.add("hidden");//the only that work
}
});
The right way:
document.getElementsByClassName("splash").forEach(el=>{el.addEventListener("load",function() {
if(!document.cookie.indexOf("visited=true")) {
setTimeout(function(){
this.classList.add("hidden");
}.bind(this), 3000);//fix of context
} else {
this.classList.add("hidden");
}
})});
You can include this IIFE at the bottom of your page so that it will be executed when the splash DOM element is ready. This way you can avoid the event listener.
I also converted your splash to use the ID splash rather than a class. If you prefer the class, when you use document.getElementsByClassName("splash") it returns an array of elements. In that case you'll have to specify which elements of the returned array you want to use (i.e. document.getElementsByClassName("splash")[0] or iterate through them).
(function() {
var splash = document.getElementById("splash");
if (document.cookie.indexOf("visited=true") === -1) {
splash.classList.remove("hidden"); // Display the splash
setTimeout(function() {
splash.classList.add("hidden"); // Hide it after the timeout
}, 500);
}
})();
#splash { position: absolute; left: 0; right: 0; top: 0; bottom: 0; background: #ddd; }
.hidden { display: none; }
Not splashed!
<div id="splash" class="hidden">Splashed!</div>
Related
I have some code that saves my dark theme after refresh:
(thanks to https://stackoverflow.com/users/519413/rory-mccrossan on this post: Day/Night Toggle Using Cookie to Save on Page Refresh )
jQuery(document).ready(function($) {
$(".theme__switch").on("click", () => {
$(".theme__switch").toggleClass("active");
$("body").toggleClass("dark__theme");
$.cookie("toggle", $(".theme__switch").hasClass('active'));
});
if ($.cookie("toggle") === "true") {
$(".theme__switch").addClass("active");
$("body").addClass("dark__theme");
}
});
The only issue I find with this solution is that it flashes the original state before adding active to the toggle. So it flashes the original white background before adding the dark theme class. Is there a solution to avoid the flicker? or is this as good as it gets
The reason is that your code under jQuery(document).ready function runs when the page fully loaded. So, you have a delay to see its result and see the flashing.
There is no choice in framework-less websites except adding a loading frame fit to the total page at first and add a code to remove it after jQuery comes ready.
Assume this for example:
html
<div id="overlay">
<!-- some loading text or elements -->
</div>
css
#overlay {
position: fixed;
top: 0;
right: 0;
left: 0;
bottom: 0;
background: #fff;
}
js
jQuery(document).ready(function($) {
$(".theme__switch").on("click", () => {
$(".theme__switch").toggleClass("active");
$("body").toggleClass("dark__theme");
$.cookie("toggle", $(".theme__switch").hasClass('active'));
});
if ($.cookie("toggle") === "true") {
$(".theme__switch").addClass("active");
$("body").addClass("dark__theme");
}
jQuery('#overlay').hide();
});
I got js from FAQ module where nodes are loaded collapsed, with "faq-answer ... collapsable collapsed" and when toggled "faq-answer ... collapsable ("collapsed" disappears).
I suspect that since .collapsed includes 'display: none;' the lazy loading script for images is not triggered.
Therefore, I'm trying to execute that script lazy.js after any faq is toggled but can't make it work.
A) Inside lazy.js i gave it a name (function runblazy($) { and inside faq.js I inserted runblazy(); just after $(this).next('div.faq-dd-hide-answer').toggleClass("collapsed"); but this interrupts the entire script, which prevents the page from properly loading ie. render links etc.
B) I also tried to run it when the class "collapsed" disappeared, but it's only being run once (during page load) and I neither could figure out how to possibly make it work with a while loop
var x = document.getElementsByClassName("faq-answer");
if (x[0].classList.contains("collapsed")) { //do nothing }
else { runblazy(); }
Which would be the better / correct approach, what am I missing?
Here is the js from faq.js :
(function ($) {
Drupal.behaviors.initFaqModule = {
attach: function (context) {
// Hide/show answer for a question.
var faq_hide_qa_accordion = Drupal.settings.faq.faq_hide_qa_accordion;
$('div.faq-dd-hide-answer', context).addClass("collapsible collapsed");
if (!faq_hide_qa_accordion) {
$('div.faq-dd-hide-answer:not(.faq-processed)', context).addClass('faq-processed').hide();
}
$('div.faq-dt-hide-answer:not(.faq-processed)', context).addClass('faq-processed').click(function() {
if (faq_hide_qa_accordion) {
$('div.faq-dt-hide-answer').not($(this)).removeClass('faq-qa-visible');
}
$(this).toggleClass('faq-qa-visible');
$(this).parent().addClass('faq-viewed');
$('div.faq-dd-hide-answer').not($(this).next('div.faq-dd-hide-answer')).addClass("collapsed");
if (!faq_hide_qa_accordion) {
$(this).next('div.faq-dd-hide-answer').slideToggle('fast', function() {
$(this).parent().toggleClass('expanded');
});
}
$(this).next('div.faq-dd-hide-answer').toggleClass("collapsed");
//ADD "runblazy();" HERE?
// Change the fragment, too, for permalink/bookmark.
// To keep the current page from scrolling, refs
// http://stackoverflow.com/questions/1489624/modifying-document-location-hash-without-page-scrolling/1489802#1489802
var hash = $(this).find('a').attr('id');
var fx, node = $('#' + hash);
if (node.length) {
fx = $('<div></div>')
.css({position: 'absolute', visibility: 'hidden', top: $(window).scrollTop() + 'px'})
.attr('id', hash)
.appendTo(document.body);
node.attr('id', '');
}
document.location.hash = hash;
if (node.length) {
fx.remove();
node.attr('id', hash);
}
// Scroll the page if the collapsed FAQ is not visible.
// If we have the toolbar so the title may be hidden by the bar.
var mainScrollTop = Math.max($('html', context).scrollTop(), $('body', context).scrollTop());
// We compute mainScrollTop because the behaviour is different on FF, IE and CR
if (mainScrollTop > $(this).offset().top) {
$('html, body', context).animate({
scrollTop: $(this).offset().top
}, 1);
}
return false;
});
I almost can't believe it, after many hours of trying, I found a very simple fix that worked.
I remembered that I suspected display:none to be the issue for lazy loading not working and read here that this should be avoided, thus changing the .css to use the below instead worked:
.faq .collapsed {
/*display: none;*/
position: absolute !important;
top: -9999px !important;
left: -9999px !important;
}
I would be still interested if there is another way as per question to run the lazy.js after the toggle?
I'm trying to get a spinner working properly in my grails app. The way I understand it, is it should work out of the box while waiting for an action to complete. It is not doing this.
I was able to get a spinner working based on some suggestions I found from google, and modified this solution: http://grails.1312388.n4.nabble.com/spinner-or-progress-indicator-td1363802.html , however this seems rather hacky to me, and not an optimal solution.
Which indicated I needed the following script:
<script type="text/javascript">
function showSpinner() {
document.getElementById('spinner').style.display = 'inline';
document.getElementById('error').style.display = 'none';
}
function hideSpinner() {
document.getElementById('spinner').style.display = 'none';
document.getElementById('error').style.display = 'none';
}
function showError(e) {
var errorDiv = document.getElementById('error')
errorDiv.innerHTML = '<ul><li>'
+ e.responseText + '</li></ul>';
errorDiv.style.display = 'block';
}
</script>
Which I employed with a grails button as such:
<g:submitToRemote value="Validate Address"
url="[action: 'standardizedList', controller: 'address']"
update="addressList" after="showSpinner();" onSuccess="hideSpinner()"
class="btn btn-primary"/><br>
<img id="spinner" style="display:none;"
src="${createLinkTo(dir: 'images', file: 'spinner.gif')}"
alt="Spinner"/>
Now I put the javascript snippet into /layouts/main.gsp, but it appears I have to add the spinner image into each page where I want it, and if I put it in the div where I want it to show, it will be overwritten when the action completes and updates the div so I have to add the spinner in the page that is responding as well as inside the response body.
When I look at the given main.gsp it has the following code in it:
<div id="spinner" class="spinner" style="display:none;">
<g:message code="spinner.alt" default="Loading…"/>
</div>
furthermore, found inside the web-app/js/ directory there is a file application.js which contains the code that I saw frequently which is supposed to add the spinner.
if (typeof jQuery !== 'undefined') {
(function($) {
$('#spinner').ajaxStart(function() {
$(this).fadeIn();
}).ajaxStop(function() {
$(this).fadeOut();
});
})(jQuery);
}
Now I have several places where I believe actions may cause a delay, and I want the spinner to tell the user it's working. So my question is two fold: 1) Am I understanding how it is supposed to be working? If so, 2) how can I make the out of the box code work properly?
Here's how I do it: make sure the following JavaScript is included in every page, e.g. by putting it in a .js file which is included in the layout:
$(document).ready(function () {
var showSpinner = function() {
$("#spinner").fadeIn('fast');
};
// Global handlers for AJAX events
$(document)
.on("ajaxSend", showSpinner)
.on("ajaxStop", function() {
$("#spinner").fadeOut('fast');
})
.on("ajaxError", function(event, jqxhr, settings, exception) {
$("#spinner").hide();
});
});
The functions above will be called every time an AJAX request is stopped, started, or returns with an error. Also include the following in the layout:
<div id="spinner" style="display:none;">
<g:img uri="/images/spinner.gif" alt="Loading..."/>
</div>
This is the content which is shown/hidden when an AJAX request starts/stops. In my case I apply the following styles, so that the spinner is shown in the center of the screen on top of any other content:
#spinner {
position: fixed;
top: 50%;
left: 50%;
margin-left: -50px; // half width of the spinner gif
margin-top: -50px; // half height of the spinner gif
z-index: 5000;
overflow: auto;
}
I'm using jQuery to show / hide a div which happens to contain an iframe. It works great with just standard 'show' and 'hide' methods.
So now I want to get a little fancy and add some effects from jQuery UI (http://jqueryui.com/effect/) but suddenly my iframes are getting reloaded every time I show / hide them.
Here is a fiddle to demonstrate: http://jsfiddle.net/BnZzk/1/
And here is the code since SO is forcing me to add it:
<style>
div {
height: 200px
}
span {
display: block;
padding-bottom: 10px;
margin-bottom: 10px;
border-bottom: 1px solid black;
}
</style>
<div>
<iframe src="http://www.wikipedia.org/"></iframe>
</div>
<input type="button" value="Go"/>
<hr/>
<div id="msgs"></div>
<script>
$(function () {
var container = $('div'),
ifrm = $('iframe'),
msgs = $('#msgs')
delay = 10; //change me to adjust delay in seconds between actions
$('input').on('click', normal);
log('First let the iframe load and then clear your network console -- click the "Go" button to get started.');
function log (msg) {
msgs.append('<span>' + msg + '</span>');
}
function normal () {
ifrm.
hide(400, function () {
log('That was a standard hide with no effect -- is your network console still empty? OK we\'ll pause for ' + delay + ' seconds and then do a standard show.');
ifrm.
delay(delay * 1000).
show(400, function () {
log('That was a show with no effect -- is you network console *still* empty? Great! Let\'s try some effects.');
log('------------------------<br/>' +
'-- Begin Effects --<br/>' +
'------------------------<br/>');
withEffect();
});
}); //hide
} //normal
function withEffect () {
log('We\'ll pause for another ' + delay + ' seconds -- get ready to watch your network console.');
ifrm.
delay(delay * 1000).
hide('fold', {mode:'hide'}, 400, function () {
log('That was a hide with effect -- is your network console flooded? Mine too :-( We\'ll wait ' + delay + ' seconds while you clear your console again.');
ifrm.
delay(delay * 1000).
show('fold', {mode:'show'}, 400, function () {
log('That was a show with effect -- is your network console flooded again? Bummer ...');
});
}); //hide
} //withEffect
});
</<script>
Any idea how I can keep the fancy effects but not refresh the content of my iframes?
It's happening because this effect reorganizes the DOM, putting a DIV wrapper around the IFRAME, so when the IFRAME is "reappended" the reload happens! You can see this behavior using the Google Chrome elements inspector.
To solve I suggest you apply the effect in a parent DIV from your IFRAME but not using the effect plugin. Check out the http://api.jquery.com/animate/, manipulating the width and height style properties.
As #Jamillo Santos's answer, 'reappend' issues of iFrame.
if you are using dialog widget or its extension of jQueryUI and want to prevent this situation,
just redefine _moveToTop() function of your widget implementation as below.
_moveToTop: function() {
return null;
},
I have the following jquery that slides a div horizontally:
$('.nextcol').click(function() {
$('.innerslide').animate({'left': '-=711px'}, 1000);
});
$('.prevcol').click(function() {
$('.innerslide').animate({'left': '+=711px'}, 1000);
});
What I want to happen is this... if the div.innerslide has a position that is left: 0px then I want to hide div.backarrow. If the position is not left: 0px, then it shows it.
Any help would be greatly appreciated.
EDIT (added HTML Markup)
<div class="backarrow prevcol">
<div id="mainleft" class="overflowhidden">
<div class="innerslide">
<div class="col">my content including next</div>
<div class="col">my content including next</div>
<div class="col">my content including next</div>
</div>
</div>
Try this:
if ($('.innerslide').css("left") == 0) {
$('div.backarrow').hide();
} else {
$('div.backarrow').show();
}
Fix for Double-Click Issue:
From what you described in your comment about the issue when the visitor double-clicks, it sounds like the double-click is causing two of the animation events to fire. To keep this from happening, you can either disable the click handler while the animation is running and re-enable it once it is finished, or you can try to write a new thread to continually check the element's position. One of these solutions is not a good idea - I'll let you figure out which one :) - but the other actually has a very simple solution that requires little change to your existing code (and may actually reduce your overhead by a teeny weeny amount):
$('.nextcol').on("click.next", function() {
$('.innerslide').animate({'left': '-=711px'}, 1000, showHideBack());
$(this).off("click.next");
});
$('.prevcol').on("click.prev", function() {
$('.innerslide').animate({'left': '+=711px'}, 1000, showHideForward());
$(this).off("click.prev");
});
Then add this this line to showHideBack() (and a complementary one to showHideForward() if you are using that):
$('.nextcol').on("click.next".....
I suggest that you write a function to set each click handler and another to remove each one. This will make your live very easy and the whole solution should reduce overhead by removing unnecessary click handlers while the animation is running.
Note: the animation method often calls its callback before the animation finishes. As such, you may wish to use a delay before calling the showHide... method(s).
Let me know if you have any questions. Good luck! :)
UPDATE:
Here is the updated version of the fiddle you gave me with all bugs ironed out. It looks like I misunderstood part of your goal in my original solution, but I straightened it out here. I have also included the updated jQuery, here:
var speed = 1000;
var back = $("div.backarrow");
var next = $(".nextcol");
var prev = $(".prevcol");
var inner = $(".innerslide");
function clickNext(index) {
next.off("click.next");
inner.animate({
'left': '-=711px'
}, speed, function() {
back.show(); //this line will only be hit if there is a previous column to show
next.delay(speed).on("click.next", function() {
clickNext();
});
});
}
function clickPrev() {
prev.off("click.prev");
inner.animate({
'left': '+=711px'
}, speed, function() {
if (inner.css("left") == "0px") {
back.delay(speed).hide();
prev.delay(speed).on("click.prev", function() {
clickPrev();
});
} else {
back.delay(speed).show();
prev.delay(speed).on("click.prev", function() {
clickPrev();
});
}
});
}
next.on("click.next", function() {
clickNext();
});
prev.on("click.prev", function() {
clickPrev();
});
I was going to also include a condition to check if you were viewing the last column, but, as I don't know what your final implementation will be, I didn't know if it would be applicable. As always, let me know if you need help or clarification on any of this. :)
You could try the step option — a callback function that is fired at each step of the animation:
$('.prevcol').click(function() {
$('.innerslide').animate({ left: '+=711px' },
{
duration: 1000,
step: function(now, fx) {
if (now === 0 ) {
$('div.backarrow').hide();
} else {
$('div.backarrow').show();
}
}
});
});
More examples of usage in this article The jQuery animate() step callback function