diff --git a/bower.json b/bower.json index e80a5cd..6e2e700 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "scrollimate", - "main": "scrollimate.js", + "main": "scrollimate.min.js", "homepage": "https://github.com/justMoritz/scrollimate", "authors": [ "Moritz Zimmer " diff --git a/readme.md b/readme.md index 847b709..80462f4 100644 --- a/readme.md +++ b/readme.md @@ -33,6 +33,12 @@ Scrollimate focuses on features and functions related to scrolling, animations ( - [SA Ripple](#saRipple) - [Basic Setup](#saRippleBasic) - [Advanced](#saUnderlineRipple) + - [springyElement](#springyelement) + - [Basic Setup](#springyelementBasic) + - [Advanced](#springyelementAdvanced) + - [scrollStuff](#scrollstuff) + - [Basic Setup](#scrollstuffbasic) + - [Advanced](#scrollstuffadvanced) ## Setup and Usage: @@ -282,7 +288,13 @@ You may not want your tabs to be indexed / indexable. Tabscroll keeps track of y Now Tabscroll can accomodate, by simply calling the `saTabs.nonIDTabs()`; method some time after you included tabscroll.js. This will not index your tabs, not write to the URL, but still keep the same markup and functionality for your tabs. +### Event +SA Tabs triggers a custom event, `satabchanged` when a tab change is invoked. You can listen to this event like so `$(document).on('satabchanged', your-custom-function());` + +### WCAG compliance + +SA Tabs auto-generated markup is using best practices according to https://www.w3.org/TR/wai-aria-practices/examples/tabs/tabs-1/tabs.html --- --- @@ -424,8 +436,53 @@ Finally, you can combile all the arments and may end up with something like this *note:* You can currently _not_ call this advanced setup via this init() method. +--- +--- + +# springyElement +(DEMO: https://codepen.io/justMoritz/pen/KYbypw) + +Adds a “springy Element” method which allows items to be slightly dragged by the curser or touch events and spring back when released, out-bounded or travelled a certain amount of pixels. + +#### BASIC SETUP: + +##### jQuery Method: + +Call on any jQuery-able element like so: `$('your-selector-here').springyElement();` + +*note:* Calling this method via the init() method is currently _untested_ ! + +#### Advanced SETUP: +Takes one argument, the amount of pixels an element can travel (20px by default) `$('your-selector-here').springyElement(200);` +*note:* You can currently _not_ call this advanced setup via this init() method. + +--- +--- + +# scrollstuff +Simple jQuery _“stuff is scrolled into view so let's add a class”_ plugin + +## Usage: +### Basic +scrollstuff Extends the jQuery Object with a new method, so the easiest usage is like this: + + $('your-selector-here').scrollstuff({}); + +### Advanced +You van also run scrollstuff with arguments + + $('your-selector-here').scrollstuff({ + classname: 'custom-class-name', + delay: 700, + repeat: true + }); + The scrollstuff object current accepts the following arguments in object notation: + * classname _(string)_ — name of the class to attach to object. `'this--nowinview'` by default. + * delay _int_ — delay until class is added, in milliseconds + * repeat _boolean_ whether or not the animation repeats when it's out and in of view again +#### That's it! Have fun and enjoy the ride and all that good stuff! diff --git a/scrollimate.js b/scrollimate.js index 01fbd9f..d37ecef 100644 --- a/scrollimate.js +++ b/scrollimate.js @@ -5,7 +5,7 @@ * _ _ _ * ___ ___ _ __ ___ | | (_)_ __ __ __ _| |_ ___ (_)___ * / __|/ __| |/ // \| | | | |/ |/ |/ ' | _|/ \ | / __| - * \__ \ (__| / | ( ) | | | | | | | | | ( ) | |_| O_/ | \__ \ + * \__ \ (__| / | ( ) | | | | | | | | | ( ) | |_| O_/ | \__ \ * |___/\___|__| \___/|_|_|_|_| |_| |_|\__\_|\__|\___(_)/ |___/ * |__/ * (c) 2019 CC Attribution 4.0 @@ -23,6 +23,7 @@ var scrollimate = (function( window, $ ){ mobileEnabled: false, isMObile: false, indexable: true, + transition_type: '', }; /* * checks and sets variable that enables parallax even on mobile in init function * */ @@ -46,28 +47,28 @@ var scrollimate = (function( window, $ ){ /* * Parallax Functionality * * / - * * - * The Parallax Animation Chain works as follows: - * 1.) Init -> - * 2.) -> saParallax (setup); * - * 3.) Scroll Listener (inside Init) -> + * The Parallax Animation Chain works as follows: + * 1.) Init -> + * 2.) -> saParallax (setup); + * + * 3.) Scroll Listener (inside Init) -> * 4.) -> _saParallaxAnimation -> * 5.) -> __saParallaxHelperFunction * - * - * 5: __saParallaxHelperFunction: - * Takes the inputOpject object generated inside _saParallaxAnimation - * to do the actual calculations as they are applied to the individual - * Elements. + * + * 5: __saParallaxHelperFunction: + * Takes the inputOpject object generated inside _saParallaxAnimation + * to do the actual calculations as they are applied to the individual + * Elements. * This function also checks whether or not the code is run in a mobile * viewport size, and if so, whether or not it has been indicated that - * the code will run in mobile (which by default it does not) + * the code will run in mobile (which by default it does not) * - * In case of non-parallax mobile, the final else statement resets all + * In case of non-parallax mobile, the final else statement resets all * transformations that may have already happened. This is useful in case - * The window is resized from a non-mobile size to a mobile size after + * The window is resized from a non-mobile size to a mobile size after * transformations have already occurred. */ var __saParallaxHelperFunction = function(inputObject){ @@ -78,10 +79,10 @@ var scrollimate = (function( window, $ ){ inputObject.winHi = 0; inputObject.elHeight = 0; } - $(inputObject.saBg).css("transform", "translate3d("+inputObject.left+", "+Math.floor((((_global.wp-inputObject.tOfSet+inputObject.winHi)/2)*inputObject.spd)+inputObject.elHeight)+"px, 0px)"); - $(inputObject.saBg).css("-ms-transform", "translate("+inputObject.left+", "+Math.floor((((_global.wp-inputObject.tOfSet+inputObject.winHi)/2)*inputObject.spd)+inputObject.elHeight)+"px)"); + $(inputObject.saBg).css("transform", "translate3d("+inputObject.left+", "+Math.floor((((_global.wp-inputObject.tOfSet+inputObject.winHi)/2)*inputObject.spd)+inputObject.elHeight)+"px, 0px)"); + $(inputObject.saBg).css("-ms-transform", "translate("+inputObject.left+", "+Math.floor((((_global.wp-inputObject.tOfSet+inputObject.winHi)/2)*inputObject.spd)+inputObject.elHeight)+"px)"); }; - + if(_global.mobileEnabled === true){ ___executeHelperFunction(); } @@ -93,13 +94,13 @@ var scrollimate = (function( window, $ ){ else{ _global.saBgLay.css('transform', 'translate3d(0, 0, 0)'); } - } + } }; /** * - * 4: _saParallaxAnimation: + * 4: _saParallaxAnimation: * Loops through each of the saBgLayers elements. (from saParallax function), * parses and splits the data-attribute arguments. First is speed, second is position. * @@ -109,13 +110,13 @@ var scrollimate = (function( window, $ ){ * If second argument is not given, element starts parallaxing from the moment page loads. * * A parallaxHelperConfig object gets passed to __saParallaxHelperFunction, - * which does the actual calculation. If an element been positioned in CSS with + * which does the actual calculation. If an element been positioned in CSS with * translateX(50%) to achieve centering, the 'left' entry in the object is changed accordingly. * Because of the horizontal nature of parallax scrolling, saParallax does not currently * support the prevervation of translateY(-50%), though this feature is planned for the future */ var _saParallaxAnimation = function($saBgLayers){ - for (i = 0 ; i < $saBgLayers.length ; i++){ + for (var i = 0 ; i < $saBgLayers.length ; i++){ var posFlag = 0, $curEl = $($saBgLayers[i]), topoffset = $curEl.offset().top, @@ -137,57 +138,57 @@ var scrollimate = (function( window, $ ){ } } - // if( topoffset < _global.wp+_global.saWinHi){ - if ( $curEl.attr("data-sabglayer") === "" ) { - $speed = 1; - } - else{ - $speed = dataBgAttributes[0]; - } + var $speed = 1; - parallaxHelperConfig = { - saBg: $saBgLayers[i], - tOfSet: topoffset, - winHi: _global.saWinHi, - spd: $speed, - elHeight: elHeight, - left: '0px' , - flag: posFlag - }; - - if ($curEl.css("transform") === "translateX(-50%)"){ - parallaxHelperConfig.left = '-50%'; - } + if ( $curEl.attr("data-sabglayer") === "" ) { + $speed = 1; + } + else{ + $speed = dataBgAttributes[0]; + } - __saParallaxHelperFunction( parallaxHelperConfig ); - // } + var parallaxHelperConfig = { + saBg: $saBgLayers[i], + tOfSet: topoffset, + winHi: _global.saWinHi, + spd: $speed, + elHeight: elHeight, + left: '0px' , + flag: posFlag + }; + + if ($curEl.css("transform") === "translateX(-50%)"){ + parallaxHelperConfig.left = '-50%'; + } + + __saParallaxHelperFunction( parallaxHelperConfig ); } }; /** * - * 2: saParallax: + * 2: saParallax: * Intial Setup, parsing and first draw: * - * Selects all the elelemts with the data-sabglayer attribute. These are stored inside - * a variable within the _global object, because it needs to stay accessible in the + * Selects all the elelemts with the data-sabglayer attribute. These are stored inside + * a variable within the _global object, because it needs to stay accessible in the * entire application, as it is continually used in the $(window).scroll and resize * functions initialized within the init method. - * + * * Method only runs functionality if there are elements present (not no elements). * Then runs the initial parallax animation. * * Also finally sets the _global.prlx to true to make sure the scroll and resize functions * in the init method only calculate and call this function if everything is set. */ - var saParallax = function () { + var saParallax = function () { - _global.saBgLay = $("[data-sabglayer]"); + _global.saBgLay = $("[data-sabglayer]"); _global.saBgLay.css('will-change', 'transform'); if( _global.saBgLay.length !== 0 ){ _saParallaxAnimation(_global.saBgLay); - + console.log('parallax initiated'); _global.prlx = true; } @@ -196,23 +197,24 @@ var scrollimate = (function( window, $ ){ /* * Smooth Anchor Scroll Functionality * * / * - * Listens to hash fragment url click, then adds a little bit of time + * Listens to hash fragment url click, then adds a little bit of time * to the scrolling based on how far the target is from the top to ensure * that the scroll motion is always about the same percieved velocity. - * Page is animated to scroll from the top to the element that that matches - * the href attribute. + * Page is animated to scroll from the top to the element that that matches + * the href attribute. * return false to prevent default */ var saScroll = function(force) { console.log('saScroll initiated'); $('[href^="#"]').click(function(){ var $this = $(this); - if( $($this.attr('href')).length && $this.attr('href') !== '#' ){ + if( $this.attr('href') !== 'javascript:void(0);' && $($this.attr('href')).length && $this.attr('href') !== '#' ){ var smoothAnchorScrollTime = 500 + (Math.floor($($this.attr("href")).offset().top))/2; + var scrollEvents = ""; if(force){ - var scrollEvents = ""; + scrollEvents = ""; }else{ - var scrollEvents = "scroll mousedown wheel DOMMouseScroll mousewheel keyup touchmove"; + scrollEvents = "scroll mousedown wheel DOMMouseScroll mousewheel keyup touchmove"; } var $page = $('html, body'); @@ -226,14 +228,14 @@ var scrollimate = (function( window, $ ){ }); return false; } - }); + }); }; /* * SA Tabs Functionality * * / - * * - * The SA Tabs Functionality Chain: + * + * The SA Tabs Functionality Chain: * 1.) saTabs -> * 1.1) -> 3 * 1.2) -> (Hash Change Listener, triggers 3) @@ -241,16 +243,16 @@ var scrollimate = (function( window, $ ){ * 2.) _saTabsSetUpPage * 3.) _saTabsHashChangeFunct * - * - * 2: _saTabsSetUpPage: + * + * 2: _saTabsSetUpPage: * Finds all anchor tabs within the data-tabscrollnavcontainer and reads the attribute. * If a type was passed here, it will control the method of change: fade, slide and none. * The active class is added to the first tab-navigation * * Then loops and targets each and every link's href-attribute found within the tabscrollnavcontainer * and adds the navigational data-attribute to each anchor tag's parent - * - * We then use this anchor to find each element, section, etc. that has the + * + * We then use this anchor to find each element, section, etc. that has the * same ID as the anchor tag we found. * * Final section sets a custom data-tabscroll attribute to each section that correspons @@ -260,34 +262,114 @@ var scrollimate = (function( window, $ ){ * default behaviour, and hiding all sections initially except the one specified. */ var _saTabsSetUpPage = function() { - $tabscrollAnchors = $("[data-tabscrollnavcontainer]").find("a").not("[data-saexclude]"); - $transition_type = $("[data-tabscrollnavcontainer]").attr("data-tabscrollnavcontainer"); - $($tabscrollAnchors[0]).parent().addClass("tabscroll_activeNavi"); + var $tabscrollAnchors = $("[data-tabscrollnavcontainer]").find("a, button").not("[data-saexclude]"); + _global.transition_type = $("[data-tabscrollnavcontainer]").attr("data-tabscrollnavcontainer"); - for ($i = 0; $i < $tabscrollAnchors.length; $i++){ + for (var $i = 0; $i < $tabscrollAnchors.length; $i++){ var $curEl = $($tabscrollAnchors[$i]), eachAnchor = $curEl.attr("href"); - $curEl.parent().attr("data-tabscrollnavi", eachAnchor.substring(1)); - $(eachAnchor).attr("data-tabscroll", eachAnchor.substring(1)); + $curEl.parent() + .attr("data-tabscrollnavi", eachAnchor.substring(1)) + .find('a, button') + .attr("id", 'roletab'+eachAnchor.substring(1)); + $(eachAnchor) + .attr("data-tabscroll", eachAnchor.substring(1)) + .attr("aria-labelledby", 'roletab'+eachAnchor.substring(1)) + .attr('tabindex', '-1') + .attr('role', 'tabpanel'); // removes link if in non-indexable version (to not interfere with app status keeping) if(!_global.indexable){ - $curEl.removeAttr("href").css('cursor', 'pointer'); + $curEl + .attr("href", 'javascript:void(0);') + .css('cursor', 'pointer') + .attr('role', 'tab'); + _saTabHelpers.saTabsPrepInactiveElement( $curEl ); $curEl.on('click', function(){ var tab_target = $(this).parent().attr("data-tabscrollnavi"); - // console.log( tab_target ); _saTabsHashChangeFunct(tab_target); }); } } - + _saTabHelpers.saTabsPrepActiveElement( $($tabscrollAnchors[0]) ); + $("[data-tabscroll]").removeAttr('id'); - $("[data-tabscroll]:first-of-type").siblings("[data-tabscroll]").hide(); + $("[data-tabscroll]:first-of-type").siblings("[data-tabscroll]").hide(); + + document.onkeydown = _saTabHelpers.saTabsHashKeyListener; + }; + + jQuery.expr[':'].focus = function( elem ) { + return elem === document.activeElement && ( elem.type || elem.href ); }; + + /** + * Collections of Methods used by saTabs + */ + var _saTabHelpers = { + /** + * Listens for keystrokes related to tabchanges + * @param {event} e event + */ + saTabsHashKeyListener: function(e){ + // First checks that the current active tab has focus, so that tabs don't change if not desired + var $activeElement = $('.tabscroll_activeNavi').find('a, button'); + if ( $activeElement.is(":focus") ) { + e = e || window.event; + + // if Home Button, activate first tab + if (e.keyCode == '36') { + $('[data-tabscrollnavi]:first-child').find('a, button').trigger('click').focus(); + } + + // if End Button, activate last button + else if (e.keyCode == '35') { + $($('[data-tabscrollnavi]')[$('[data-tabscrollnavi]').length - 1]).find('a, button').trigger('click').focus(); + } + + // if left arrow, activate previous tab + else if (e.keyCode == '37') { + $('.tabscroll_activeNavi').prev('[data-tabscrollnavi]').find('a, button').trigger('click').focus(); + } + + // if right arrow, activate next tab + else if (e.keyCode == '39') { + $('.tabscroll_activeNavi').next('[data-tabscrollnavi]').find('a, button').trigger('click').focus(); + } + + } + }, + + /** + * Changes the markup of an element to be the active tab. + * @param {object} $input LINK, not the PARENT + */ + saTabsPrepActiveElement: function($input){ + $input + .attr('aria-selected', 'true') + .attr('tabindex', '0') + .addClass('this--active') + .parent().addClass("tabscroll_activeNavi"); + }, + + /** + * Changes the markup of an element to be the INactive tab. + * @param {object} $input LINK, not the PARENT + */ + saTabsPrepInactiveElement: function($input){ + $input + .attr('aria-selected', 'false') + .attr('tabindex', '-1') + .removeClass('this--active') + .parent().removeClass("tabscroll_activeNavi"); + }, + }; + + /** * - * 3: _saTabsHashChangeFunct: + * 3: _saTabsHashChangeFunct: * Called both initially in saTabs and also on each Hash (URL fragment) change, monitured * by the saTabs Method. * @@ -298,10 +380,10 @@ var scrollimate = (function( window, $ ){ * In order to parse the users navigational input. * * If there is no hash (or it set to 'all'), show only the first section of tab content. - * If there is a hash-link active, function will hide all tabs, fade in only the tab with + * If there is a hash-link active, function will hide all tabs, fade in only the tab with * the data-tabscroll attribute corresponding to the link that was clicked. * - * Finally, also checks for the 'fade' and 'slide' transition types, and executes different + * Finally, also checks for the 'fade' and 'slide' transition types, and executes different * funcionality, which I would like to break out into different functions eventually. */ var _saTabsHashChangeFunct = function( masterinput ) { @@ -309,11 +391,11 @@ var scrollimate = (function( window, $ ){ /* checks the current location, matches it to the element containing the link, and adds correct class */ var __activeClassHelperFunction = function(inputLoc){ var $naviEls = $('[data-tabscrollnavi]'); - for (i=0; i<$naviEls.length; i++){ + for (var i=0; i<$naviEls.length; i++){ var $curEl = $($naviEls[i]); if( $curEl.data('tabscrollnavi') === inputLoc ){ - $('.tabscroll_activeNavi').removeClass('tabscroll_activeNavi'); - $curEl.addClass('tabscroll_activeNavi'); + _saTabHelpers.saTabsPrepInactiveElement( $('.tabscroll_activeNavi').find('a, button') ); + _saTabHelpers.saTabsPrepActiveElement( $curEl.find('a, button') ); } } }; @@ -324,16 +406,15 @@ var scrollimate = (function( window, $ ){ location = masterinput; } else{ - location = String(document.location); + location = String(document.location); location = location = location.split("#")[1]; // if the location is not passed as master input, we use the has instead! } // check if location resolves to a tab and sets the exists variable to true var $allTabs = $("[data-tabscroll]"); var exists = false; - for(i=0; i < $allTabs.length; i++){ + for(var i=0; i < $allTabs.length; i++){ var curtab = $($allTabs[i]).attr('data-tabscroll'); - // console.log( curtab ); if( curtab === location ){ exists = true; } @@ -341,34 +422,43 @@ var scrollimate = (function( window, $ ){ if (location === undefined || location === 'all' || exists === false){ var $firstTab = $("[data-tabscroll]:first-of-type"); - $firstTab.show(); - $firstTab.addClass('activeTab'); + $firstTab.show(); + $firstTab.addClass('activeTab').attr('tabindex', '0'); if(_global.indexable){ window.location.hash = $firstTab.attr('data-tabscroll'); } } else{ - $("[data-tabscroll]").hide().removeClass('activeTab'); + $("[data-tabscroll]").hide().removeClass('activeTab'); - if ( $transition_type === 'fade') { - $("[data-tabscroll='"+location+"']").fadeIn().addClass('activeTab'); + if ( _global.transition_type === 'fade') { + $("[data-tabscroll='"+location+"']") + .fadeIn() + .addClass('activeTab') + .attr('tabindex', '0'); __activeClassHelperFunction(location); - } - else if ( $transition_type === 'slide') { - $("[data-tabscroll='"+location+"']").slideDown().addClass('activeTab'); + } + else if ( _global.transition_type === 'slide') { + $("[data-tabscroll='"+location+"']") + .slideDown() + .addClass('activeTab') + .attr('tabindex', '0'); __activeClassHelperFunction(location); } else{ - $("[data-tabscroll='"+location+"']").show().addClass('activeTab'); + $("[data-tabscroll='"+location+"']") + .show() + .addClass('activeTab') + .attr('tabindex', '0'); __activeClassHelperFunction(location); } - } + } }; /** * - * 1: saTabs: + * 1: saTabs: * Runs the _saTabsSetUpPage function, the initial instance of the _saTabsHashChangeFunct, * then also monitors the hash change to run the _saTabsHashChangeFunct as needed. * Only Monitors hash change if the indexable setting is set to true @@ -391,28 +481,28 @@ var scrollimate = (function( window, $ ){ } }, 100); } - + $(window).load(function(){ _saTabsHashChangeFunct(); }); console.log('saTabs initiated'); - }; + }; /** - * Public Helper Method that sets the SA TABS status to non-indexable + * Public Helper Method that sets the SA TABS status to non-indexable */ var nonIDTabs = function(){ _global.indexable = false; }; - + /** * - * SA Scroll Class: + * SA Scroll Class: * * Function adds class class to target element while and ONLY WHILE scrollingElement is scrolled - */ + */ var saScrollClass = function ( $scrollingElement, $target, classname ){ $target.addClass('scrollprep'); var timer, @@ -438,18 +528,17 @@ var scrollimate = (function( window, $ ){ /** * - * SA Accordion Functionality: - * - * Function consists mainly of the __saAccordionHelper function, + * SA Accordion Functionality: + * + * Function consists mainly of the __saAccordionHelper function, * which is called on initial load, and also every time the window is resized, * for resizing. * - * + * */ var saAccordion = function(element, mainwidthinpercent, type, imageaspectratio){ var __saAccordionHelper = function(){ var $element = $(element); - // console.log( $element ); if (mainwidthinpercent === undefined){ mainwidthinpercent = '50'; } @@ -458,36 +547,36 @@ var scrollimate = (function( window, $ ){ var restwidth = (100-mainwidthinpercent)/(numEl-1); // desktop functionality - if ( $(window).width() > 767) { - // if no imageaspectratio is given, default to 33% + if ( $(window).width() > 767) { + // if no imageaspectratio is given, default to 33% if(imageaspectratio === undefined){ imageaspectratio = "33%" ;} $element.css('float', 'left'); - $element.css('padding-bottom', imageaspectratio); + $element.css('padding-bottom', imageaspectratio); $element.css('width', restwidth+'%').removeClass('active'); - $($element[0]).css('width', mainwidthinpercent+'%').addClass('active'); - } + $($element[0]).css('width', mainwidthinpercent+'%').addClass('active'); + } // mobile functionality else{ $element.css('width', '100%'); $element.css('height', 0).css('padding-bottom', restwidth+'%').removeClass('active'); - $($element[0]).css('height', 0).css('padding-bottom', mainwidthinpercent+'%').addClass('active'); + $($element[0]).css('height', 0).css('padding-bottom', mainwidthinpercent+'%').addClass('active'); } // if no type is given, default to click if (type === undefined){ type = 'click'; } $element.on(type, function(){ // desktop functionality - if ( $(window).width() > 767) { + if ( $(window).width() > 767) { // console.log(restwidth); $element.css('width', restwidth+'%').removeClass('active'); - $(this).css('width', mainwidthinpercent+'%').addClass('active'); - } + $(this).css('width', mainwidthinpercent+'%').addClass('active'); + } // mobile functionality else{ - $element.css('width', '100%'); + $element.css('width', '100%'); $element.css('height', 0).css('padding-bottom', restwidth+'%').removeClass('active'); - $(this).css('height', 0).css('padding-bottom', mainwidthinpercent+'%').addClass('active'); - } + $(this).css('height', 0).css('padding-bottom', mainwidthinpercent+'%').addClass('active'); + } }); setTimeout(function(){ @@ -503,7 +592,7 @@ var scrollimate = (function( window, $ ){ /** * saAccordion (jQuery extension) - * + * * Extends the jQuery Object with the saUnderline Method (jQuery.saUnderline) */ jQuery.fn.saAccordion = function(mainwidthinpercent, type, imageaspectratio) { @@ -516,28 +605,28 @@ var scrollimate = (function( window, $ ){ /** * saUnderline * - * Targets either all anchor tags, - * or and FULL jQuery selector + * Targets either all anchor tags, + * or and FULL jQuery selector * * wraps each word in a link tag in a span with class of underline, * for the purpose of better stying underlines via pseudo classes */ var saUnderline = function($target){ - $target = $target || $('a'); + $target = $target || $('a'); var $allLinks = $target; - for(i=0; i<$allLinks.length; i++){ + for(var i=0; i<$allLinks.length; i++){ var cur = $allLinks[i], words = $(cur).text().split(" "); $(cur).empty(); $.each(words, function(i, v) { $(cur).append($("").text(v+' ')); }); - } + } }; /** * saUnderline (jQuery extension) - * + * * Extends the jQuery Object with the saUnderline Method (jQuery.saUnderline) */ jQuery.fn.saUnderline = function() { @@ -556,30 +645,32 @@ var scrollimate = (function( window, $ ){ * @ (optional) interaction: See below * @ (optional) noclass: true/FALSE * - * You can set the ripple color in 3 ways: + * You can set the ripple color in 3 ways: * - use default: White. Nothin is needed other than the call the function one time on the page * - When initializing saRipple, you can set the color by passing it as an argument. * - add the data-ripple attribute the dom element, and put the complete CSS color therein * * Notes: * Parses input arguments, adds styles to head of page, adds ripplestyles base-class to all targets - * The listens to the desired interaction and appends the div to inside that element, based on the - * mouse cursor position. + * The listens to the desired interaction and appends the div to inside that element, based on the + * mouse cursor position. * If noclass is set to true, the target element will not inherit the basic styles required to make * ripple effect work. (display: inline-block, overflow: hidden, position relative) */ var saRipple = function($input){ - + + var fallbackcolor = '#ffffff', + inttype = 'click', + noclass = false, + $target; + if(typeof $input !== 'undefined'){ $target = $input.target || $('.ripple'); - var fallbackcolor = $input.color || '#ffffff'; - var inttype = $input.interaction || 'click'; - var noclass = $input.noclass || false; + fallbackcolor = $input.color || '#ffffff'; + inttype = $input.interaction || 'click'; + noclass = $input.noclass || false; }else{ $target = $('.ripple'); - var fallbackcolor = '#ffffff'; - var inttype = 'click'; - var noclass = false; } if($("#scrollimate__ripplestyles").length){}else{ @@ -588,64 +679,65 @@ var scrollimate = (function( window, $ ){ if(!noclass){ $target.addClass('ripplestyles'); - } + } var _execute = function(event, passedthis){ - var $div = $('
'), - btnOffset = $(passedthis).offset(), - xPos = event.pageX - btnOffset.left, - yPos = event.pageY - btnOffset.top; - - $div.addClass('ripple-effect'); - var $ripple = $(".ripple-effect"); - - $ripple.css("height", $(passedthis).height()); - $ripple.css("width", $(passedthis).height()); - var color = $(passedthis).data("ripplecolor") || fallbackcolor; - $div.css({ - top: yPos - ($ripple.height()/2), - left: xPos - ($ripple.width()/2), - background: color - }).appendTo($(passedthis)); - - window.setTimeout(function(){ - $div.remove(); - }, 1500); - } - + var $div = $('
'), + btnOffset = $(passedthis).offset(), + xPos = event.pageX - btnOffset.left, + yPos = event.pageY - btnOffset.top; + + $div.addClass('ripple-effect'); + var $ripple = $(".ripple-effect"); + + $ripple.css("height", $(passedthis).height()); + $ripple.css("width", $(passedthis).height()); + var color = $(passedthis).data("ripplecolor") || fallbackcolor; + $div.css({ + top: yPos - ($ripple.height()/2), + left: xPos - ($ripple.width()/2), + background: color + }).appendTo($(passedthis)); + + window.setTimeout(function(){ + $div.remove(); + }, 1500); + }; + var reseter = false; + if(inttype === 'mouseover'){ - $target.on(inttype, function (event) { + $target.on(inttype, function (event) { if(reseter === true){ _execute(event, this); reseter = false; } }); - + $target.on('mouseout', function(){ reseter = true; }); } else{ - $target.on(inttype, function (event) { + $target.on(inttype, function (event) { _execute(event, this); }); } }; - + /** * saRipple (jQuery extension) - * + * * Extends the jQuery Object with the “saRipple” Method (jQuery.saRipple) * The target (the element this method was run on) is added to the input, * then it calls the (scollimate-internal) Method (scrollimate.saRipple) - * + * * @param input JSON (Optional) * @return appended jQuery Object */ jQuery.fn.saRipple = function(input) { var passedinput; - for(i=0; i < this.length; i++){ + for(var i=0; i < this.length; i++){ if(input === undefined){ passedinput = { target: $(this[i]) }; } @@ -659,15 +751,259 @@ var scrollimate = (function( window, $ ){ }; + /** + * Helper Functions for springyElement + * @type {Object} + */ + var _springyElementHelpers = { + + /** + * Stores the original position of the element + * @param {jQUery} the individual jQuery element + */ + setInitialPosition: function($element){ + $element.attr('data-springycompoundx', 0); + $element.attr('data-springycompoundy', 0); + }, + + + /** + * Puts the element back into the original position + * @param {jQUery} the individual jQuery element + */ + resetPosition: function($element){ + $element.css('transform', 'translate(0,0'); + $element.attr('data-springycompoundx', 0); + $element.attr('data-springycompoundy', 0); + }, + + + /** + * Checks and moves the element + * @param {event} passed event from element + * @param {jQUery} the individual jQuery element + * @param {int} max amount of px element can travel + */ + mouseUpdateFunct: function(e, $element, modifier){ + + // variables to hold the input + var inputx, inputy; + + // determines whether or not input is touch event + // (if `touches` table is present in the originalEvent) + // or if not, use the mouse position instead + if( e.originalEvent.touches === undefined ){ + inputx = e.pageX; + inputy = e.pageY; + }else{ + inputx = e.originalEvent.touches[0].pageX; + inputy = e.originalEvent.touches[0].pageY; + } + + // calculates the difference between the old and the new + // position, and then updates variable store (on the + // element for the next round + var differenceX = inputx - $element.attr('data-springyx'); + var differenceY = inputy - $element.attr('data-springyy'); + $element.attr('data-springyx', inputx); + $element.attr('data-springyy', inputy); + + // the transform property used below (if condition is met) + var newXposition = Number( $element.attr('data-springycompoundx') ) + differenceX; + var newYposition = Number( $element.attr('data-springycompoundy') ) + differenceY; + + // stores the new position + $element.attr('data-springycompoundx', newXposition); + $element.attr('data-springycompoundy', newYposition); + + // if the total amount of pixels the element will have moved + // does not exceed the max. amount of pixels the element is allowed to move + if( + newXposition > modifier || + newYposition > modifier || + newXposition < -modifier || + newYposition < -modifier + ){ + if( newXposition > modifier*12 || + newYposition > modifier*12 || + newXposition < -modifier*12 || + newYposition < -modifier*12 + ){ + $element.css('transform', 'translate('+ 0 + 'px, ' + 0 + 'px'); + } + }else{ + $element.css('transform', 'translate('+ newXposition + 'px, ' + newYposition + 'px'); + } + }, + + + /** + * Adds class to all springy elements and creates + * unique styletag in head, if not already present + * @param {jQUery} the element to apply the springy functionality to + */ + elementStyleSetup: function($passedElementName){ + $passedElementName.addClass('scrollimate-springyelement'); + if( !$("#scrollimate__springyelementstyles").length ){ + $('').appendTo($('head')); + } + }, + }; + + + /** + * The method which jQuery is being extended by + * @param {jQuery} jQuery selector calling the method + * @param {int} max distance the element can travel in pixels + */ + var springyElement = function($passedElement, distance){ + + // default setting for distance. + distance = distance || 20; + + // sets required styles for elements + _springyElementHelpers.elementStyleSetup($passedElement); + + // execute for each individual element + $passedElement.each(function(){ + + var $element = $(this); + + // set the intial data-attributes + _springyElementHelpers.setInitialPosition($element); + + // set the inital position of where the mouse entered the element + $element.on('mouseenter', function(e){ + $element.attr('data-springyx', e.pageX); + $element.attr('data-springyy', e.pageY); + }); + + // Element being moved over listener + $element.on('mousemove', function(e){ + _springyElementHelpers.mouseUpdateFunct(e, $element, distance); + }); + + // Element being touched and moved listener + $element.on('touchmove', function(e){ + _springyElementHelpers.mouseUpdateFunct(e, $element, distance, true); + }); + + // resets the element when the mouse leaves + $element.on('mouseup, mouseout', function(){ + _springyElementHelpers.resetPosition($element); + }); + + // resets the element when the touch is released + $element.on('touchend', function(){ + _springyElementHelpers.resetPosition($element); + }); + + }); // end .each() + + }; + + + /** + * Extends the jQuery object with the springyElement Method + * @param {int} max distance the element can travel in pixels + */ + jQuery.fn.springyElement = function(distance) { + springyElement( $(this), distance ); + return this; + }; + + + /** + * Helper Functions for _scrollStuff + * @type {Object} + */ + var _scrollStuffHelpers = { + /** + * checks if element in in view and applies class + * @param {jQuery Selector} The full jQuery selector you want to use + * passed from inview() initial call + */ + inviewChecker: function($passedselector, passedrepeat, passedclassname, passeddelay){ + if($passedselector !== undefined){ + $passedselector.each(function(){ + // console.log( _global.wp + _global.saWinHi ) + // console.log( $(this).offset().top ) + if( _global.wp + _global.saWinHi > $(this).offset().top ){ + var $passedthis = $(this); + setTimeout(function(){ + $passedthis.addClass(passedclassname); + }, passeddelay); + }else{ + if(passedrepeat){ + $(this).removeClass(passedclassname); + } + } + }); + } + }, + + height: function($selector){ + return $selector.outerHeight(true); + }, + }; + + + /** + * @param {object} Takes the input parameters from original call + */ + var inview = function( selector, calledinput ){ + + var repeat = false; + var classname = 'this--nowinview'; + var delay = 0; + + if (typeof(calledinput) !== 'undefined') { + repeat = calledinput.repeat || repeat; + classname = calledinput.classname || classname; + delay = calledinput.delay || delay; + } + + _global.wp = $(window).scrollTop(); + + _scrollStuffHelpers.inviewChecker(selector, repeat, classname, delay); + + $(window).scroll( function(){ + _scrollStuffHelpers.inviewChecker(selector, repeat, classname, delay); + }); // end window scroll + }; + + + /** + * + * Extends the jQuery Object with the scrollstuff Method, + * which calls the inview method with an input object as defined below + * + * @param {object} Takes the following input parameters + * + * { + * repeat: {boolean} whether or not the animation repeats + * when it's out and in of view again + * classname: {string} name of the class to attach to object + * 'this--nowinview' by default. + * delay: {int} delay until class is added, in milliseconds + * } + * + */ + jQuery.fn.scrollstuff = function(inputobject) { + inview(this, inputobject); + return this; + }; + + - /** - * Init Function - * + /** + * Init Function + * * On Document Ready, calculates the height of viewport (window Height) * * Parses the arguments-array given to the init method's call, loops through - * them and then executes the function with the corresponding name. + * them and then executes the function with the corresponding name. * Some Error Checking Applies, and see comments further down for classic * and Fallback Methods * @@ -678,14 +1014,15 @@ var scrollimate = (function( window, $ ){ var init = function(input){ $(function(){ - _global.saWinHi = "innerHeight" in window ? window.innerHeight : document.documentElement.offsetHeight; + _global.saWinHi = "innerHeight" in window ? window.innerHeight : document.documentElement.offsetHeight; + _global.wp = $(window).scrollTop(); // checks if the init method was called with the old way, // if so, loops through the array and executes each function by name var calledWithArr = Object.prototype.toString.call(input) == '[object Array]'; if( calledWithArr ){ - console.log( 'Classic Init Method classically calling the following Methods: ' ) - for(i=0; i < input.length; i++){ + console.log( 'Classic Init Method classically calling the following Methods: ' ); + for(var i=0; i < input.length; i++){ console.log( input[i] ); _executeFunctionByName("scrollimate."+input[i]+""); } @@ -693,7 +1030,7 @@ var scrollimate = (function( window, $ ){ // Otherwise, loops through each argument given as an object (new way) // Key should be the function name, input[key] the arguments to the function }else{ - console.log( 'Init Method calling the following Methods: ' ) + console.log( 'Init Method calling the following Methods: ' ); for (var key in input){ var current = window['scrollimate'][key]; @@ -706,20 +1043,20 @@ var scrollimate = (function( window, $ ){ if (typeof current === "function"){ var isArr = Object.prototype.toString.call(input[key]) == '[object Array]'; if( isArr ){ - current.apply(null, input[key]) + current.apply(null, input[key]); }else{ - current.call(null, input[key]) + current.call(null, input[key]); } } - } + } } //end else/calledWithArr $(window).resize(function(){ - _global.saWinHi = "innerHeight" in window ? window.innerHeight : document.documentElement.offsetHeight; + _global.saWinHi = "innerHeight" in window ? window.innerHeight : document.documentElement.offsetHeight; if( _global.prlx ){ - _saParallaxAnimation(_global.saBgLay); + _saParallaxAnimation(_global.saBgLay); } }); // end window resize @@ -727,7 +1064,7 @@ var scrollimate = (function( window, $ ){ _global.wp = $(window).scrollTop(); if( _global.prlx ){ - _saParallaxAnimation(_global.saBgLay); + _saParallaxAnimation(_global.saBgLay); } }); // end window scroll @@ -736,10 +1073,14 @@ var scrollimate = (function( window, $ ){ }; - /* + /* * Public Methods */ return{ + enableMobile: enableMobile, + init: init, + inview: inview, + nonIDTabs: nonIDTabs, saAccordion: saAccordion, saParallax: saParallax, saRipple: saRipple, @@ -747,8 +1088,6 @@ var scrollimate = (function( window, $ ){ saScrollClass: saScrollClass, saTabs: saTabs, saUnderline: saUnderline, - init: init, - nonIDTabs: nonIDTabs, - enableMobile: enableMobile, + springyElement: springyElement, }; })(window, jQuery); diff --git a/scrollimate.min.js b/scrollimate.min.js new file mode 100644 index 0000000..f35416a --- /dev/null +++ b/scrollimate.min.js @@ -0,0 +1 @@ +var scrollimate=function(t,a){var e={wp:0,prlx:!1,speed:1,saBgLay:[],saWinHi:"",mobileEnabled:!1,isMObile:!1,indexable:!0,transition_type:""},s=function(a){for(var e=t,s=a.split("."),n=s.pop(),o=0;o767?n():e.saBgLay.css("transform","translate3d(0, 0, 0)")},o=function(t){for(var s=0;s1&&(o=1,"center"===c[1]?l=-l/2*c[0]:l*=c[1]);var f=1;f=""===r.attr("data-sabglayer")?1:c[0];var d={saBg:t[s],tOfSet:i,winHi:e.saWinHi,spd:f,elHeight:l,left:"0px",flag:o};"translateX(-50%)"===r.css("transform")&&(d.left="-50%"),n(d)}};jQuery.expr[":"].focus=function(t){return t===document.activeElement&&(t.type||t.href)};var r={saTabsHashKeyListener:function(e){a(".tabscroll_activeNavi").find("a, button").is(":focus")&&("36"==(e=e||t.event).keyCode?a("[data-tabscrollnavi]:first-child").find("a, button").trigger("click").focus():"35"==e.keyCode?a(a("[data-tabscrollnavi]")[a("[data-tabscrollnavi]").length-1]).find("a, button").trigger("click").focus():"37"==e.keyCode?a(".tabscroll_activeNavi").prev("[data-tabscrollnavi]").find("a, button").trigger("click").focus():"39"==e.keyCode&&a(".tabscroll_activeNavi").next("[data-tabscrollnavi]").find("a, button").trigger("click").focus())},saTabsPrepActiveElement:function(t){t.attr("aria-selected","true").attr("tabindex","0").addClass("this--active").parent().addClass("tabscroll_activeNavi")},saTabsPrepInactiveElement:function(t){t.attr("aria-selected","false").attr("tabindex","-1").removeClass("this--active").parent().removeClass("tabscroll_activeNavi")}},i=function(s){a(document).trigger("satabchanged");var n,o=function(t){for(var e=a("[data-tabscrollnavi]"),s=0;s767?(void 0===o&&(o="33%"),r.css("float","left"),r.css("padding-bottom",o),r.css("width",l+"%").removeClass("active"),a(r[0]).css("width",s+"%").addClass("active")):(r.css("width","100%"),r.css("height",0).css("padding-bottom",l+"%").removeClass("active"),a(r[0]).css("height",0).css("padding-bottom",s+"%").addClass("active")),void 0===n&&(n="click"),r.on(n,function(){a(t).width()>767?(r.css("width",l+"%").removeClass("active"),a(this).css("width",s+"%").addClass("active")):(r.css("width","100%"),r.css("height",0).css("padding-bottom",l+"%").removeClass("active"),a(this).css("height",0).css("padding-bottom",s+"%").addClass("active"))}),setTimeout(function(){r.css("transition","all 0.6s")},1e3)};r(),t.addEventListener("resize",function(){r()})};jQuery.fn.saAccordion=function(t,a,e){return l(this,t,a,e),this};jQuery.fn.saUnderline=function(){return scrollimate.saUnderline(a(this)),this};var c=function(e){var s,n="#ffffff",o="click",r=!1;void 0!==e?(s=e.target||a(".ripple"),n=e.color||"#ffffff",o=e.interaction||"click",r=e.noclass||!1):s=a(".ripple"),a("#scrollimate__ripplestyles").length||a('\x3c!--[if IE 9]>"),r=a(s).offset(),i=e.pageX-r.left,l=e.pageY-r.top;o.addClass("ripple-effect");var c=a(".ripple-effect");c.css("height",a(s).height()),c.css("width",a(s).height());var f=a(s).data("ripplecolor")||n;o.css({top:l-c.height()/2,left:i-c.width()/2,background:f}).appendTo(a(s)),t.setTimeout(function(){o.remove()},1500)},l=!1;"mouseover"===o?(s.on(o,function(t){!0===l&&(i(t,this),l=!1)}),s.on("mouseout",function(){l=!0})):s.on(o,function(t){i(t,this)})};jQuery.fn.saRipple=function(t){for(var e,s=0;se||l>e||i<-e||l<-e?(i>12*e||l>12*e||i<12*-e||l<12*-e)&&a.css("transform","translate(0px, 0px"):a.css("transform","translate("+i+"px, "+l+"px")},m=function(t){t.addClass("scrollimate-springyelement"),a("#scrollimate__springyelementstyles").length||a('').appendTo(a("head"))},u=function(t,e){e=e||20,m(t),t.each(function(){var t=a(this);f(t),t.on("mouseenter",function(a){t.attr("data-springyx",a.pageX),t.attr("data-springyy",a.pageY)}),t.on("mousemove",function(a){p(a,t,e)}),t.on("touchmove",function(a){p(a,t,e,!0)}),t.on("mouseup, mouseout",function(){d(t)}),t.on("touchend",function(){d(t)})})};jQuery.fn.springyElement=function(t){return u(a(this),t),this};var v=function(t,s,n,o){void 0!==t&&t.each(function(){if(e.wp+e.saWinHi>a(this).offset().top){var t=a(this);setTimeout(function(){t.addClass(n)},o)}else s&&a(this).removeClass(n)})},h=function(s,n){var o=!1,r="this--nowinview",i=0;void 0!==n&&(o=n.repeat||o,r=n.classname||r,i=n.delay||i),e.wp=a(t).scrollTop(),v(s,o,r,i),a(t).scroll(function(){v(s,o,r,i)})};jQuery.fn.scrollstuff=function(t){return h(this,t),this};return{enableMobile:function(){e.mobileEnabled=!0},init:function(n){a(function(){if(e.saWinHi="innerHeight"in t?t.innerHeight:document.documentElement.offsetHeight,e.wp=a(t).scrollTop(),"[object Array]"==Object.prototype.toString.call(n)){console.log("Classic Init Method classically calling the following Methods: ");for(var r=0;r").text(e+" "))})}},springyElement:u}}(window,jQuery); \ No newline at end of file