I'm trying to do this origami-like animation, but Snap.svg only selects one path – the first one to animate.
I already tried to change path = s.select( 'path' ) to path = s.selectAll( 'path' ), as said on documentation, but it doesn't work.
Any ideas of where I'm wrong?
Here is the code: http://jsfiddle.net/HGwXL/
selectAll will return a set. So you will need to iterate over that set, probably with a forEach....
var pathSet = s.selectAll( 'path' );
pathSet.forEach( function( myPath ) {
var pathConfig = {
from : myPath.attr( 'd' ),
to : myPath.attr( 'data-path-hover' )
};
el.addEventListener( 'mouseenter', function() {
myPath.animate( { 'path' : pathConfig.to }, speed, easing );
} );
el.addEventListener( 'mouseleave', function() {
myPath.animate( { 'path' : pathConfig.from }, speed, easing );
} );
});
jsfiddle here
You could probably also do it slightly differently, and have just one handler that just iterated over the paths an animates them as well, but its probably a bit fiddlier.
Related
On my site, I am trying to fix the navigation so that when the browser is getting resized from desktop to mobile size, the mobile menu works. I have the mobile menu working on initial load, and the desktop navigation working on initial load, but when I run the script in a $(window).on('resize', function() {} and click an item as depicted in my script, the event fires always +1 each time the window was rested after a resize.
What I mean is, if I load the page, scale it into mobile size, click the menu and a dropdown item, the click event will fire once. Resize the window out and then back in, the click event will fire now 2 times, then 3, and so on, depending on how many times the browser was resized.
I'm not sure exactly what is going on in my resize script that is screwing everything up and I'm at my wits end at trying to figure it out. Normally people aren't sitting there resizing their browser from desktop to mobile, but my boss does when he show's clients a beta of their site and wants this to never be an issue.
Here is my resize script:
(function( $ ) {
var id,
$body = $('body'),
$window = $( window ),
$navSlider = $('.nav-slider'),
$navMask = $( '.nav-mask' ),
$navToggler = $( '.navbar-toggler' ),
$parent = $( '.menu-item-has-children' ),
$parentLink = $( '.dropdown-toggle' ),
$childContainer = $( '.dropdown-menu' );
$window.on( 'resize', function( e ) {
clearTimeout(id);
id = setTimeout(function() {
close();
var width = $window.width();
if ( width < 992 ) {
setHeightToNav();
$navMask.on( 'click', function() { close() } );
$navToggler.on( 'click', function() { open() } );
$parentLink.on( 'click', function( e ) {
e.preventDefault();
var $this = $( this );
$this.data( 'clicked', true );
console.log( $this.parent() );
} )
}
if ( width >= 992 ) {
resetNavHeight();
console.clear();
}
}, 500 );
} );
function setHeightToNav() {
if ( $body.hasClass( 'logged-in' ) ) {
var $wpAdminBar = $( '#wpadminbar' ).outerHeight();
$navSlider.css( { top: $wpAdminBar + 'px' } );
}
var $navHeight = $( '#header-container' ).outerHeight();
$navSlider.css( { marginTop: $navHeight + 'px' } );
}
function resetNavHeight() {
if ( $body.hasClass( 'logged-in' ) ) {
$navSlider.css( { top: 0 + 'px' } );
}
$navSlider.css( { marginTop: 0 + 'px' } );
}
function close() {
$body.removeClass( 'has-active-menu' );
setTimeout( function() {
$navSlider.removeClass( 'toggling' );
$parent.removeClass( 'show' );
$parentLink.attr( 'aria-expanded', false );
$childContainer.removeClass( 'show' ).removeAttr( 'style' );
$parentLink.data('clicked', false);
}, 250 );
console.log('close()');
}
function open() {
$body.addClass( 'has-active-menu' );
$navSlider.addClass( 'toggling' );
}
})( jQuery );
I've tried my script both with AND without the setTimeout function and it happens exactly the same.
On the project, we are using Bootstrap 4, with the Bootstraps Dropdown._clearMenus(); function commented out in the right places as it was causing conflicts with the functionality I wanted with the navigation.
A link to a site where you can see this is here. It's a WordPress site as well if that matters for anything.
Any help is appreciated. I've been at this for several hours and am at my wits end.
.on( 'click', function ) does not set the event listener, it adds an event listener. Try doing off('click') before setting it if you really need to set this listener here.
But note that any other 'click' listener for this element will also be removed.
That's for the quick fix. You could do better, but that would require more work (track with a boolean if you just changed "display mode", and add or remove the event listeners only then, for example).
Is it possible to write a script that turns off a script at a certain screen resolution? I have these svg's that do not work properly under 480px, and I would like to turn them off. I am super new to this. Any help would be amazing.
<script>
(function() {
function init() {
var speed = 250,
easing = mina.easeinout;
[].slice.call ( document.querySelectorAll( '#grid > .container > .row .activities_home > a' ) ).forEach( function( el ) {
var s = Snap( el.querySelector( 'svg' ) ), path = s.select( 'path' ),
pathConfig = {
from : path.attr( 'd' ),
to : el.getAttribute( 'data-path-hover' )
};
el.addEventListener( 'mouseenter', function() {
path.animate( { 'path' : pathConfig.to }, speed, easing );
} );
el.addEventListener( 'mouseleave', function() {
path.animate( { 'path' : pathConfig.from }, speed, easing );
} );
} );
}
init();
})();
</script>
You can attach an event listener to the document DOMContentLoaded and window resize events to query the screen resolution (the window.innerWidth and window.innerHeight properties).
If the resolution falls below your breakpoint, then you can remove the event listeners from each of the paths (you will need a reference to the function in order to do so).
If you don't need to handle resize events and just want to initially disable the script based on the resolution, then you can do something simple like replacing:
init();
with
if (window.innerWidth > 480)
init();
I am writing a JQuery plugin for a project I'm working on which turns from tabbed content on desktop devices to an accordion on mobile devices. I've used JQuery Boilerplate (https://github.com/jquery-boilerplate/jquery-boilerplate/blob/master/dist/jquery.boilerplate.js) as an initial pattern for my plugin.
The plugin is called on any element with the class ".tabs2accordion" as shown here:
$(".tabs2accordion").tabs2Accordion({state:"desktop"});
The plugin works as expected if there is only one element with ".tabs2accordion" class on a page but starts to malfunction as soon as another element with the same class is added to the page. I've created a codepen of the basic code to demo the issue. To show the issue, on a window size of >768px try clicking any of the titles and observe how the content below changes as each title is clicked. Next uncomment the block of HTML and try clicking on the titles again.
http://codepen.io/decodedcreative/pen/MyjpRj
I have tried looping through each element with the class "tabs2accordion" like this:
$(".tabs2accordion").each(function(){
$(this).tabs2Accordion({state:"desktop"});
});
But this didn't fix the issue either.
Any ideas?
I have not used jQuery Boilerplate, but I believe the problem here is with your variable called plugin.
Nowhere in your code do you declare a variable called plugin. When I stop the debugger in Plugin.prototype.showTabContent, I can evaluate window.plugin and it returns the global value for plugin.
In the constructor for Plugin, the first line reads plugin= this;. Since plugin is not defined, it is declaring the variable at global scope on the window object.
The fix is to pass a reference to the plugin object when setting up the $().on() hook. The data passed is available in the event handlers via the event parameter that is passed in the data property.
Here is the solution (at http://codepen.io/shhQuiet/pen/JXEjMV)
(function($, window, document, undefined) {
var pluginName = "tabs2Accordion",
defaults = {
menuSelector: ".tabs2accordion-menu",
tabContentSelector: ".tabs2accordion-content"
};
function Plugin(element, options) {
this.element = element;
this.$element = $(this.element);
this.options = $.extend({}, defaults, options);
this.$menu = $(this.element).find(this.options.menuSelector),
this.$tabs = $(this.element).find(this.options.tabContentSelector),
this.$accordionTriggers = $(this.element).find(this.$tabs).find("h3");
this._defaults = defaults;
this._name = pluginName;
this.init();
}
Plugin.prototype = {
init: function() {
//Set all the tab states to inactive
this.$tabs.attr("data-active", false);
//Set the first tab to active
this.$tabs.first().attr("data-active", true);
//If you click on a tab, show the corresponding content
this.$menu.on("click", "li", this, this.showTabContent);
//Set the dimensions (height) of the plugin
this.resizeTabs2Accordion({
data: this
});
//If the browser resizes, adjust the dimensions (height) of the plugin
$(window).on("resize", this, this.resizeTabs2Accordion);
//Add a loaded class to the plugin which will fade in the plugin's content
this.$element.addClass("loaded");
console.log(this.$element);
},
resizeTabs2Accordion: function(event) {
var contentHeight;
var plugin = event.data;
if (!plugin.$element.is("[data-nested-menu]")) {
contentHeight = plugin.$tabs.filter("[data-active='true']").outerHeight() + plugin.$menu.outerHeight();
} else {
contentHeight = plugin.$tabs.filter("[data-active='true']").outerHeight();
}
plugin.$element.outerHeight(contentHeight);
},
showTabContent: function(event) {
var $target;
var plugin = event.data;
plugin.$menu.children().find("a").filter("[data-active='true']").attr("data-active", false);
plugin.$tabs.filter("[data-active='true']").attr("data-active", false);
$target = $($(this).children("a").attr("href"));
$(this).children("a").attr("data-active", true);
$target.attr("data-active", true);
plugin.resizeTabs2Accordion({data: plugin});
return false;
},
showAccordionContent: function(event) {
var plugin = event.data;
$("[data-active-mobile]").not($(this).parent()).attr("data-active-mobile", false);
if ($(this).parent().attr("data-active-mobile") === "false") {
$(this).parent().attr("data-active-mobile", true);
} else {
$(this).parent().attr("data-active-mobile", false);
}
}
};
$.fn[pluginName] = function(options) {
return this.each(function() {
if (!$.data(this, "plugin_" + pluginName)) {
$.data(this, "plugin_" + pluginName, new Plugin(this, options));
}
});
};
})(jQuery, window, document);
$(window).on("load", function() {
$(".tabs2accordion").tabs2Accordion({
state: "desktop"
});
});
I rewrote your code following jQuery's Plugin creation standard.
http://codepen.io/justinledouxmusique/pen/GZrMgB
Basically, I did two things:
Moved away from using data attributes for styling (switched to using an .active class instead)
Moved away from using this everywhere, as it bring a whole wave of binding issues...
$.fn.tabs2Accordion loops through all the selectors, and applies $.tabs2Accordion. It also returns the selector for chaining (it's a standard in jQuery).
Then, all the internal methods are function expressions which are in the same scope as all your old this "variables". This simplifies the code greatly as you can refer to those variables without passing them in as a parameter or without having to .bind( this ) somehow.
Finally, the old init() function is gone. Instead, I put the code at the end of the $.tabs2Accordion function.
Hope this helps!
(function ( window, $ ) {
$.tabs2Accordion = function ( node, options ) {
var options = $.extend({}, {
menuSelector: '.tabs2accordion-menu',
tabContentSelector: '.tabs2accordion-content'
}, options )
var $element = $( node ),
$menu = $element.find( options.menuSelector ),
$tabs = $element.find( options.tabContentSelector ),
$accordionTriggers = $tabs.find( 'h3' )
var resizeTabs2Accordion = function () {
$element.outerHeight( !$element.is( '[data-nested-menu]' )
? $element.find( 'div.active' ).outerHeight() + $menu.outerHeight()
: $element.find( 'div.active' ).outerHeight() )
}
var showTabContent = function () {
var $this = $( this ) // This will be the clicked element
$menu
.find( '.active' )
.removeClass( 'active' )
$element
.find( '.active' )
.removeClass( 'active' )
$( $this.find( 'a' ).attr( 'href' ) )
.addClass( 'active' )
$this
.find( 'a' )
.addClass( 'active' )
resizeTabs2Accordion()
return false
}
var showAccordionContent = function () {
var $this = $( this ),
$parent = $this.parent(),
mobileIsActive = $parent.data( 'active-mobile' )
$( '[data-active-mobile]' )
.not( $parent )
.data( 'active-mobile', false )
$parent
.data( 'active-mobile', mobileIsActive ? false : true )
}
// The equivalent of init()
$tabs
.removeClass( 'active' )
.first()
.addClass( 'active' )
$element.addClass( 'loaded' )
$menu.on( 'click', 'li', showTabContent )
$( window ).on( 'resize', resizeTabs2Accordion )
resizeTabs2Accordion()
console.log( $element )
}
$.fn.tabs2Accordion = function ( options ) {
this.each( function ( index, node ) {
$.tabs2Accordion( node, options )
})
return this
}
})( window, jQuery )
$( window ).on( 'load', function () {
$( '.tabs2accordion' ).tabs2Accordion({
state: 'desktop'
})
})
So there is some code from codepen.io http://codepen.io/karolpodlesny/pen/npKqu. It is uploaded here: http://fredricarms.com/javatestindex.html.
Now, the HTML, CSS and JavaScript for making the boxes expand and do all the cool things, are working how they are supposed to, being in separate files, so is the modernizr. I also the know that the js is being called because in the boxlayout.js I wrote some code to bring up and alert box and it worked just fine. So I am guessing that codepen fixes the code so it runs perfectly. I just don't know what is wrong with the code in the boxlayout.js that is not working on my server. Please help and thank you so much. Below is the code in the boxlayout js file.
var Boxlayout = (function() {
var $el = $( '#bl-main' ),
$sections = $el.children( 'section' ),
// works section
$sectionWork = $( '#bl-work-section' ),
// work items
$workItems = $( '#bl-work-items > li' ),
// work panels
$workPanelsContainer = $( '#bl-panel-work-items' ),
$workPanels = $workPanelsContainer.children( 'div' ),
totalWorkPanels = $workPanels.length,
// navigating the work panels
$nextWorkItem = $workPanelsContainer.find( 'nav > span.bl-next-work' ),
// if currently navigating the work items
isAnimating = false,
// close work panel trigger
$closeWorkItem = $workPanelsContainer.find( 'nav > span.bl-icon-close' ),
transEndEventNames = {
'WebkitTransition' : 'webkitTransitionEnd',
'MozTransition' : 'transitionend',
'OTransition' : 'oTransitionEnd',
'msTransition' : 'MSTransitionEnd',
'transition' : 'transitionend'
},
// transition end event name
transEndEventName = transEndEventNames[ Modernizr.prefixed( 'transition' ) ],
// support css transitions
supportTransitions = Modernizr.csstransitions;
function init() {
initEvents();
}
function initEvents() {
$sections.each( function() {
var $section = $( this );
// expand the clicked section and scale down the others
$section.on( 'click', function() {
if( !$section.data( 'open' ) ) {
$section.data( 'open', true ).addClass( 'bl-expand bl-expand-top' );
$el.addClass( 'bl-expand-item' );
}
} ).find( 'span.bl-icon-close' ).on( 'click', function() {
// close the expanded section and scale up the others
$section.data( 'open', false ).removeClass( 'bl-expand' ).on( transEndEventName, function( event ) {
if( !$( event.target ).is( 'section' ) ) return false;
$( this ).off( transEndEventName ).removeClass( 'bl-expand-top' );
} );
if( !supportTransitions ) {
$section.removeClass( 'bl-expand-top' );
}
$el.removeClass( 'bl-expand-item' );
return false;
} );
} );
// clicking on a work item: the current section scales down and the respective work panel slides up
$workItems.on( 'click', function( event ) {
// scale down main section
$sectionWork.addClass( 'bl-scale-down' );
// show panel for this work item
$workPanelsContainer.addClass( 'bl-panel-items-show' );
var $panel = $workPanelsContainer.find("[data-panel='" + $( this ).data( 'panel' ) + "']");
currentWorkPanel = $panel.index();
$panel.addClass( 'bl-show-work' );
return false;
} );
// navigating the work items: current work panel scales down and the next work panel slides up
$nextWorkItem.on( 'click', function( event ) {
if( isAnimating ) {
return false;
}
isAnimating = true;
var $currentPanel = $workPanels.eq( currentWorkPanel );
currentWorkPanel = currentWorkPanel < totalWorkPanels - 1 ? currentWorkPanel + 1 : 0;
var $nextPanel = $workPanels.eq( currentWorkPanel );
$currentPanel.removeClass( 'bl-show-work' ).addClass( 'bl-hide-current-work' ).on( transEndEventName, function( event ) {
if( !$( event.target ).is( 'div' ) ) return false;
$( this ).off( transEndEventName ).removeClass( 'bl-hide-current-work' );
isAnimating = false;
} );
if( !supportTransitions ) {
$currentPanel.removeClass( 'bl-hide-current-work' );
isAnimating = false;
}
$nextPanel.addClass( 'bl-show-work' );
return false;
} );
// clicking the work panels close button: the current work panel slides down and the section scales up again
$closeWorkItem.on( 'click', function( event ) {
// scale up main section
$sectionWork.removeClass( 'bl-scale-down' );
$workPanelsContainer.removeClass( 'bl-panel-items-show' );
$workPanels.eq( currentWorkPanel ).removeClass( 'bl-show-work' );
return false;
} );
}
return { init : init };
})();
Your code relies entirely on jQuery, however you haven't included jQuery on your live site. On your CodePen example you're using jQuery 1.9.1:
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js">
You can continue to use the Google-hosted version, or if you want to host it yourself you can download jQuery from jQuery's own website.
The problem is that you need to initialize your BoxLayout once your document has finished loading (all those jQuery variables you're using inside your library won't be defined because their equivalent dom elements are not yet rendered), that's why you need to init everything when dom is ready.
Add this line of code to the bottom of your boxlayout.js file:
$(document).ready(function() {
Boxlayout.init();
});
Just for testing purposes, open up the web developer console on your website, and run this js code:
Boxlayout.init();
You'll see everything will work just fine.
I'm trying to add a class of "active" to the current slide in carouFredSel, and I can't get it to work. The closest I could get it to work was to add it on the first slide, using trigger("currentVisible"), but it doesn't update.
Help! Thanks :)
So far, I used this function ( it doesn't work on page load though and it seems to be a lot of code for that simple task)
Maybe someone has an idea how to simplify this and also make it work on page load
function highlight( items ) {
items.filter(":eq(1)").addClass("active");
}
function unhighlight( items ) {
items.removeClass("active");
}
$('#foo').carouFredSel({
scroll : {
onBefore: function( data ) {
unhighlight( data.items.old );
},
onAfter : function( data ) {
highlight( data.items.visible );
}
},
});
Here's an update that should work fine on page load and scroll:
Here are more details about the trigger event.
var $highlight = function() {
var $this = $("#foo");
var items = $this.triggerHandler("currentVisible"); //get all visible items
$this.children().removeClass("active"); // remove all .active classes
items.filter(":eq(1)").addClass("active"); // add .active class to n-th item
};
$('#foo').carouFredSel({
scroll : {
onAfter : $highlight
},
onCreate : $highlight
});
scroll : {
onAfter : $highlight
}
solved my issue