I am trying to use 2 jQuery navigation scripts on one page (Superfish for desktops and FlexNav for mobile). I am currently using matchMedia along with the polyfill by Paul Irish to respond to CSS3 media query state changes within JavaScript.
The current code is only accomplishing 50% of the overall goal. If you access the web page initially with a window size equal to or greater than 999px wide then you get Superfish and if you initially access the web page with a window size less than 999px then you get FlexNav. The problem occurs when you resize the window above or below 999px as both scripts become active.
// media query event handler
if (matchMedia) {
var mq = window.matchMedia("(min-width: 999px)");
mq.addListener(WidthChange);
WidthChange(mq);
}
// media query change
function WidthChange(mq) {
if (mq.matches) {
$("ul.sf-menu").superfish({
delay: 350,
speed: 400,
});
} else {
$("ul.flexnav").flexNav({
'animationSpeed': '250',
'transitionOpacity': true,
'buttonSelector': '.menu-button',
'hoverIntent': false
});
}
}
As much as I would like to get this working with matchMedia, I am open to all suggestions.
Update: Thanks to Stephan's suggestion I now have the following code:
jQuery(document).ready(function () {
// add destroy function for FlexNav
flexNavDestroy = function () {
$('.touch-button').off('touchstart click').remove();
$('.item-with-ul *').off('focus');
}
// media query event handler
if (matchMedia) {
var mq = window.matchMedia("(min-width: 999px)");
mq.addListener(WidthChange);
WidthChange(mq);
}
// media query change
function WidthChange(mq) {
if (mq.matches) {
if (typeof (flexNav) != "undefined") {
flexNavDestroy();
}
superfish = $("ul.sf-menu").superfish({
delay: 350,
speed: 400,
});
} else {
if (typeof (superfish) != "undefined") {
superfish.superfish('destroy');
}
flexNav = $("ul.flexnav").flexNav({
'animationSpeed': '250',
'transitionOpacity': true,
'buttonSelector': '.menu-button',
'hoverIntent': false
});
}
}
});
Remaining Issue:
The destroy function for FlexNav is only partially destroying it.
The best way would probably be to destroy the other plugin when you're activating one.
If I look in the source of Superfish there is a destroy function which does this, but flexNav doesn't have such a function. You can create one though:
flexNavDestroy = function(){
$('.touch-button').off('touchstart click').remove();
$(('.item-with-ul *').off('focus');
}
Then you could do this:
function WidthChange(mq) {
if (mq.matches) {
if(typeof(flexNav) != "undefined") {
flexNavDestroy();
}
superfish = $("ul.sf-menu").superfish({
delay: 350,
speed: 400,
});
} else {
if(typeof(superfish) != "undefined") {
superfish.superfish('destroy');
}
flexNav = $("ul.flexnav").flexNav({
'animationSpeed': '250',
'transitionOpacity': true,
'buttonSelector': '.menu-button',
'hoverIntent': false
});
}
}
UPDATE
I've looked a little bit more into FlexNav, and there's a few things I missed.
I think the styles are colliding because FlexNav sets a lot of styles by default. We can easily prevent that by using two classes: One for flexnav styling (the default .flexnav) that we can remove to hide all it's styles, and one for binding the javascript function (that will always stay there, or we can't re-attach it).
I generally like to prepend any classes that are meant as JS hooks with js-, so in my example (below) I replaces the .flexnav class on the menu with .js-flexnav. Then to activate flexnav you have to add this line just before you call $('ul.flexnav').flexNav()
$('.js-flexnav').addClass('flexnav');
In the destroy function you will have to remove the class again, which I will show shortly.
In addition, I'm not sure how Superfish does the showing and hiding, but since FlexNav collapses all submenus, it's also safe to say you should re-show them so that Superfish can do it's own thing.
The updated destroy function to reflect this:
function flexNavDestroy(){
$('.touch-button').off('touchstart click').remove();
$('.item-with-ul *').off('focus');
$('.js-flexnav').removeClass('flexnav').find('ul').show(); // removes .flexnav for styling, then shows all children ul's
}
Here's a jsFiddle that shows activating/deactivating flexNav with the new code: http://jsfiddle.net/9HndJ/
Let me know if this does the trick for you!
here is an alternative path :
once page is loaded :
cache the menu in a jquery object, clone it & instantiate both plugin one on each clone
$menucontainer= $("#menu_container");
$memufish = $menucontainer.find(".menu");
$menuflex=$menufish.clone();
$menufish.superfish().detach();
$menuflex.prependTo($menucontainer).flexnav().detach();
(they are loaded anyway so it's no big deal even if most of the time one won't be needed, it will be there & ready just in case - however test if you can instantiate on the clone without appending it to the DOM)
depending on width append / prepend the required one
$menuflex.prependTo($menucontainer);
on change width detach one reattach the other
$menufish.detach();
$menuflex.prependTo($menucontainer);
you could also work your way checking if plugin was instantiated on a width change (in order to not instantiate uselessly onload) but in any way I believe the use of clone() and detach() are very much adapted to solve easily your problem. The destroy way seems to be a hassle, lots of work (for the script as well when some user is raving with window resize) loss of time & a risk of many bugs to me ( expect more and more lag at every destroy re instantiate - with detach() no worries)
cons : will use a bit more memory overhaul
pros :
script will work less & it will be real fast to switch from one to the other
you could make a plugin from this and add other menu plugin to your app very easily without worry about conflict and how to destroy
Related
I have created a slider/carousel like the one below on left - Desktop view.
I would like it to be switching to Pager based slider on mobile screens - like the one on the right side.
I have used this script for desktop slider -
https://www.jqueryscript.net/rotator/Simplest-3D-Image-Carousel-Plugin-For-jQuery-Carousel-js.html
Any help will be great, Thanks!
You will have to use 2 plugins for this. As far as I can tell there is no "pager" option for the plugin you are using. And then, using JavaScript you should destroy current plugin and initialize new one. Which could also be a problem since I don't see any sort of destroy method for your plugin. So ok, it would look something like this.
function init3DSlider() {
$('.your-container').carousel({
your: 'options'
})
}
function initPagerSlider() {
$('.your-container').somePagerPlugin({
// ...your options
})
}
// Function for checking which slider should turn on.
function turnOnSliderDependingOnResolution () {
if(window.matchMedia('(min-width: 768px)').matches) {
init3DSlider()
// ...somehow destroy pager slider
} else {
initPagerSlider()
// ...somehow destroy 3d slider
}
}
// Run turnOnSliderDependingOnResolution function on window resize.
window.addEventListener('resize', turnOnSliderDependingOnResolution)
Since this 3d slider doesn't have destroy method, try using this: http://ub4.underblob.com/remove-jquery-plugin-instance/
Or you can use a more simple solution, and that is to duplicate your slider, initialize both sliders (3D and pager). And then using CSS media queries you would hide one or the other.
Not exactly optimal but it will work.
I'm making a slideshow with full screen functionality (FancyBox 3), it's using the RequestFullScreen method on the container div, which means any other element in the body is not accessible in full screen mode. (correct me if i'm wrong)
I would like to include an AddThis Expanding Share button in the slideshow, but because it's created dynamically by the AddThis js, it appends the Smart Layers before the end of the body, not at the end of the slideshow container div therefore it's not included in the full screen slideshow.
I couldn't find any info on Smart Layers DOM placement in the AddThis API.
What I've tried is seems like a bad idea, to manually appendTo the necessary divs to the slideshow container after the divs are created by AddThis, I managed to "cut and paste" the '.at-expanding-share-button' and it's working so far, but I can't "catch" the '#at-expanded-menu-host' (which is the layer created by AddThis for more sharing options, with the dark background), and I'm not even sure if this method will work properly...
Any help would be appreciated.
I figured it out! :) I thought I share my experience/solution, if anyone has similar difficulties.
What won't work and why:
First I've tried to communicate with the AddThis API to tell it to put its layers to a premade container, which looks like impossible and the AddThis team also told me that there is no solution for manual layer DOM placement, it's always appending those to the end of the body (at least for now, maybe they will implement this option into their API).
Then I've tried to manually append those layers to the Fancy-Box container, which was a dead end because when the slideshow closes, it removes its container from the markup, so the AddThis layers disappeared and I couldn't reinit it (maybe others have some solution for that, but I just couldn't figure it out).
By the way, the "More Sharing Options" layer is created when the share + button is clicked.
My solution:
Instead of appending the layers to the dynamic slideshow container, I've created a static div at the end of the body, appended the layers to it when they are created, and set the Fancy-Box parent div to my container (note that Fancy-Box full screen functionality makes its own div into full screen, so I had to use my own full screen function for the container with the layers and the slideshow).
I've used sindresorhus's screenfull for easier/cross-browser full screen functions.
var FullScreenContainer = $('#container');
// Check if AddThis Layers are ready
var checkLayers = setInterval(function() { appendLayers(); }, 1000);
// Append the layers to FullScreenContainer
function appendLayers() {
var layers = $('.at-expanding-share-button, #_atssh');
if(layers.length > 0){
addthis.layers.refresh();
layers.appendTo(FullScreenContainer);
clearInterval(checkLayers);
console.log('layers added');
}
else {
console.log('not found')
}
}
// Check for more layers when the share icon clicked
$(document).on('click', ".at-expanding-share-button-toggle", function() {
var checkMoreLayers = setInterval(function() { catchLayers(); }, 1000);
function catchLayers() {
var morelayers = $('#at-expanded-menu-host');
if(morelayers.length > 0){
morelayers.appendTo(FullScreenContainer);
clearInterval(checkMoreLayers);
console.log('more layers added');
}
else {
console.log('did not found more')
}
}
});
// Don't forget to disable the full screen function in Fancy-Box,
// then call them when necessary (onInit, clickSlide, clickOutside
// and the close button)
function enterFullscreen() {
screenfull.request($('#container')[0]);
}
function exitFullscreen() {
if (screenfull.isFullscreen) {
screenfull.exit();
$.fancybox.getInstance().close();
}
if (!screenfull.isFullscreen) {
$.fancybox.getInstance().close();
}
}
And you are good to go. I hope this helps for anybody else! :)
I am using http://jquery.malsup.com/cycle2/api/ and I am trying to destroy cycle2 slider on window resize event when I detect mobile device. Unfortunatly it returns the following two errors:
[cycle2] slideshow must be initialized before sending commands; "destroy" ignored
[cycle2] slideshow must be initialized before sending commands; "reinit" ignored
Maybe someone could help, what am I doing wrong? Here is the code:
$(function() {
var slider = $('.slider').cycle();
condition = true;
//destroy onload under condition
if(condition){
slider.cycle('destroy');
}
//destroy on resize
$(window).on('resize',function() {
condition = true; //Will be function to recondition let´s say it's true by now
if(condition){
slider.cycle('destroy');
} else {
slider.cycle('reinit');
}
});
});
Thank you.
I know this is an old question, but I was trying to figure this out too, and after careful reading of the docs, this is what I came up with.
So I use the data attributes to set my options on my slideshow. I really like this feature.
For simplicity sake, here is my opening cycle2 div
<div data-cycle-carousel-visible="3"
data-cycle-carousel-fluid="true"
data-cycle-fx="carousel"
data-cycle-prev="#carousel-prev"
data-cycle-next="#carousel-next"
class="cycle-slideshow cycle-front-page-slideshow"
>
Notice that I did add the cycle-slideshow class so Cycle2 auto initializes, then I also added another class of cycle-front-page-slideshow just in case I have other slideshows on my site I can target just this one.
My javascript then looks like this.
function check_window_size( opts ){
// Check if the max-width of window is 899px; window.matchMedia is a native javascript function to check the window size.
var w899 = window.matchMedia( "(max-width: 899px)" );
// if it is max-width of 899px, then set the number of items in the cycle2 carousel slideshow to 2, else show 3
// to see if it matches, you would use the variable and grab the matches array; this will return true or false if window size is max-width 899px
if( w899['matches'] ) {
opts.carouselVisible = 2;
}else{
opts.carouselVisible = 3;
}
}
This is where you would target your slideshow (mine using the .cycle-front-page-slideshow class)
// Grab the cycle2 slideshow initialized from the data attributes on the DIV above
$('.cycle-front-page-slideshow').on('cycle-bootstrap', function(e, opts, API) {
// run the check_window_size function to get initial window size, just in case they are max-width 899px already
check_window_size( opts );
// When window is resized, send the options to the check_window_size function so we can manipulate it
window.onresize = function() {
check_window_size( opts );
};
});
Also note that if you want to use the Carousel functionality, you must download the cycle2 carousel transition plugin from http://jquery.malsup.com/cycle2/download/
Hope this helps out other people.
Looks like you're destroying the slider here:
if(condition){
slider.cycle('destroy');
}
You could do it like that:
$(function() {
var $W = $(window),
slider = $('.slider').cycle();
$W.on('resize',function() {
if ($W.width() < 768) // width of device
slider.cycle('destroy');
});
});
Apologies for the general question but I have been looking to implement an image showcase similar to the new Flickr layout like in this example
The difficulty I am having is in the responsive design. I have looked at various plugins including: Isotope, Wookmark, Grid-a-licious but all of these solutions either leave uneven margins/gutters when the browser window is resized or don't align at the bottom in the style of pinterest where things are just stacked vertically on rows.
I was wondering if anyone knew of a plugin that would resize images to completely fill the width of rows and align all images correctly at the bottom like on Flickr.
Alternatively it would be great to know where to get started on the javascript for something like this?
I know it's too late but this library does what you need:
http://masonjs.com/
in my travels i have worked on a few responsive sites via css3 media queries. here is a modified solution i found and used in cases where i need to trigger javascript on the event of changes in the media queries:
// define a query here
var theQuery = "(min-width: 960px)";
var mql = window.matchMedia( theQuery );
var TO = setTimeout( function(){}, 100);
var handleMediaChange = function (mediaQueryList) {
if (mediaQueryList.matches) {
// the media query evaluates to true
clearTimeout(TO);
window.state = 0;
TO = setTimeout( function(){
// javascript actions go here
}, 100 );
} else {
// #media query evaluates to false
clearTimeout(TO);
window.state = 0;
TO = setTimeout( function(){
// javascript actions go here
}, 100 );
}
}
mql.addListener(handleMediaChange);
handleMediaChange(mql);
in essence it allows us to define a query and then add a listener to watch the window object for changes that would trigger our query. when it triggers i have defined two sets of javascript one each for media query evaluating to true or false, but you could remove this if condition and just have any change execute some script...
give this a try and hope it helps
I'm building Windows 8 app in JavaScript. What I'm trying to do is to slide the html element out of the screen and then change its "display" property to "none":
var panelContainer = $('#panelContainer');
panelContainer.animate({ right: '-400px' }, 200, function () {
panelContainer.hide();
});
But this code doesn't work correctly: it just immediately hides the element without animation.
I've also tried:
var panelContainer = $('#panelContainer');
panelContainer.animate({ right: '-400px' }, 200, function () {
panelContainer.hide(200);
});
and it works, but it's a hack: I don't want to change the opacity when animating and I don't need to have additional timeout for hiding.
I've found that jQuery UI library has extended show and hide methods that do that, but I would like not to reference this library just for one call. I'm aware that there is a WinJS.UI.Flyout that performs similar operation, but it's not applicable in my case. Any ideas how this can be done?
The problem was that jQuery does not put hide animation into its animation queue by default. That's why my initial code was hiding the html element first and then animating it. The solution for that is to call hide with the parameter that explicitly specifies that hide call should be queued:
panelContainer.hide({queue: true});