CSS Animations and page refresh on previous page with iOS/Android - javascript

I'm trying to achieve a transition effect when changing pages on a project I'm working on. The desired effect is the following:
Page fades in with an opacity from 0 to 1, thanks to a #keyframes
On any link clicked, the page opacity is changed back to 0, with another #keyframes
I've added an animationEnd event listener which will force the opacity of the page to 0 (in order to avoid a weird flash) and then will go to the link.
This is working fine on the latest versions of Chrome, FF and IE, but I'm having issues with iOS and Android. When hitting the "back" button, the page is showed with its latest state (opacity: 0). I believe this is a native solution which forces the CSS/JS not to reload again, but it's quite annoying as I can't find a way to "refresh" assets when hitting the "back" button.
Does anyones have a solid solution for this kind of issue ?
--
As an example, I've copied below a sample of my current JS :
if ("animation" in document.body.style) {
var animationEnd = "animationend"
}
else {
var animationEnd = "webkitAnimationEnd"
}
var link = document.querySelectorAll('a');
for (var i = 0; i < link.length; i++) {
link.item(i).addEventListener("click", function(e) {
var linktarget = this.getAttribute('href');
if (linktarget != '#') {
e.preventDefault();
page.className += ' ' + 'fadeout';
var fadeout = document.querySelector('.fadeout');
fadeout.addEventListener(animationEnd, function() {
this.style.opacity = '0';
window.location.href = linktarget;
});
}
});
}

Try wrapping your code you want to "refresh" into pageshow:
window.addEventListener("pageshow", function() {
... your code ...
}, false);

Related

Angularjs Digest Cycle lock scroll

Good afternoon guys,
I have the following problem I have a navigation tab on the same screen that reference the four section in html. These sections gain display block or display none according to the active tab, and each tab has a different content that is already loaded in the background via ajax, until the ai everything works the problem is when I navigate between tabs have boxes with different sizes of Height, apparently the cycle of digestion of Angularjs takes to recognize this resizing and the scroll of the page is locked until it detects the new height, somebody has any idea of ​​how I can proceed to solve this, I am using IONIC FRAMEWORK.
Below is an image that shows how the content boxes are, remembering that only one is visible at a time, and when I looked at the page I noticed that the locking occurs in the style =" transform: translate3d (0px, 0px, 0px) Scale (1);
After a few attempts I left this bug aside and went to other functions and when making a correction in the directive that controls the tabs I noticed that I could recalculate the scroll size as soon as a new tab was loaded added the following line click function:
$ionicScrollDelegate.resize(); //Fix box resize bug when trasition between tabs
The function go be so:
$timeout(function () {
childElem = angular.element(document.querySelector(".child")).prop('children');
if(childElem.length){
for (var i = 0; i < childElem.length; i++) {
angular.element(childElem[i]).on('click', function (e) {
var tab = angular.element(e.path ? e.path[0] : e.target).prop('id').replace(/tab/g, "");
$rootScope.$emit('aba_ativa_feed', tab); // informa a view feed qual a aba ativa
for (var i = 0; i < tabs.length; i++) {
if(i == tab - 1) {
ativos[i] = true;
} else {
ativos[i] = false;
angular.element(document.querySelector("#tab-nav-block-id-".concat(i+1))).addClass('ng-hide');
}
}
angular.element(document.querySelector("#tab-nav-block-id-".concat(tab))).removeClass('ng-hide');
$ionicScrollDelegate.scrollTo(0, blocksScrollPosition[tab - 1], true);
scope.active = ativos;
scope.$apply();
$ionicScrollDelegate.resize(); //Fix box resize bug when trasition between tabs
})
}
}
}, 200)
for more details look here

How to prevent iOS keyboard from pushing the view off screen with CSS or JS

I have a responsive web page that opens a modal when you tap a button. When the modal opens, it is set to take up the full width and height of the page using fixed positioning. The modal also has an input field in it.
On iOS devices, when the input field is focused, the keyboard opens. However, when it opens, it actually pushes the full document up out of the way such that half of my page goes above the top of the viewport. I can confirm that the actual html tag itself has been pushed up to compensate for the keyboard and that it has not happened via CSS or JavaScript.
Has anyone seen this before and, if so, is there a way to prevent it, or reposition things after the keyboard has opened? It's a problem because I need users to be able to see content at the top of the modal while, simultaneously, I'd like to auto-focus the input field.
first
<script type="text/javascript">
$(document).ready(function() {
document.ontouchmove = function(e){
e.preventDefault();
}
});
then this
input.onfocus = function () {
window.scrollTo(0, 0);
document.body.scrollTop = 0;
}
For anyone stumbling into this in React, I've managed to fix it adapting #ankurJos solution like this:
const inputElement = useRef(null);
useEffect(() => {
inputElement.current.onfocus = () => {
window.scrollTo(0, 0);
document.body.scrollTop = 0;
};
});
<input ref={inputElement} />
I struggled with this for awhile, I couldn't find something that worked well for me.
I ended up doing some JavaScript hackery to make it work.
What I found was that Safari wouldn't push the viewport if the input element was in the top half of the screen. That was the key to my little hack:
I intercept the focus event on the input object and instead redirect the focus to a invisible (by transform: translateX(-9999px)). Then once the keyboard is on screen (usually 200ms or so) I trigger the focus event on the original element which has since animated on screen.
It's a kind of complicated interaction, but it works really well.
function ensureOffScreenInput() {
let elem = document.querySelector("#__fake_input");
if (!elem) {
elem = document.createElement("input");
elem.style.position = "fixed";
elem.style.top = "0px";
elem.style.opacity = "0.1";
elem.style.width = "10px";
elem.style.height = "10px";
elem.style.transform = "translateX(-1000px)";
elem.type = "text";
elem.id = "__fake_input";
document.body.appendChild(elem);
}
return elem;
}
var node = document.querySelector('#real-input')
var fakeInput = ensureOffScreenInput();
function handleFocus(event) {
fakeInput.focus();
let last = event.target.getBoundingClientRect().top;
setTimeout(() => {
function detectMovement() {
const now = event.target.getBoundingClientRect().top;
const dist = Math.abs(last - now);
// Once any animations have stabilized, do your thing
if (dist > 0.01) {
requestAnimationFrame(detectMovement);
last = now;
} else {
event.target.focus();
event.target.addEventListener("focus", handleFocus, { once: true });
}
}
requestAnimationFrame(detectMovement);
}, 50);
}
node.addEventListener("focus", handleFocus, { once: true });
Personally I use this code in a Svelte action and it works really well in my Svelte PWA clone of Apple Maps.
Video of it working in a PWA clone of Apple Maps
You'll notice in the video that the auto-complete changes after the animation of the input into the top half of the viewport stabilizes. That's the focus switch back happening.
The only downside of this hack is that the focus handler on your original implementation will run twice, but there are ways to account for that with metadata.
you could also do this if you don't want scrollTo the top(0, 0)
window.scrollBy(0, 0)
const handleResize = () => {
document.getElementById('header').style.top = window.visualViewport.offsetTop.toString() + 'px'
}
if (window && window.visualViewport) visualViewport.addEventListener('resize', handleResize)
Source: https://rdavis.io/articles/dealing-with-the-visual-viewport
In some situations this issue can be mitigated by re-focusing the input element.
input.onfocus = function () {
this.blur();
this.focus();
}
Both IOS8 and Safari bowsers behave the same for input.focus() occuring after page load. They both zoom to the element and bring up the keyboard.(Not too sure if this will be help but have you tried using something like this?)
HTML IS
<input autofocus>
JS is
for (var i = 0; i < 5; i++) {
document.write("<br><button onclick='alert(this.innerHTML)'>" + i + "</button>");
}
//document.querySelector('input').focus();
CSS
button {
width: 300px;
height: 40px;
}
ALso you will have to use a user-agent workaround, you can use it for all IOS versions
if (!/iPad|iPhone|iPod/g.test(navigator.userAgent)) {
element.focus();
}

Photoswipe 4.0: Initiate 'swipe to next' programatically

SITUATION
I have been trying to trigger the 'slide to next picture' animation when the NEXT button is clicked, but i have not found a solution for this.
There is an ongoing discussion about this on GitHub, but it is only about adding the option for a slide animation, not about how to actually do it with PS as it is right now.
There was an option for it in 3.0 but as 4.0 is a complete rewrite it does not work anymore.
QUESTION
Instead of just 'jumping' to the next/prev picture when an arrow is clicked, i need the 'slide transition' that is also used when swiping/dragging the image.
There is no option to trigger that, so how can i manually trigger this effect with JS?
PhotoSwipe Slide Transitions
So, I added slide transitions to Photoswipe, and it's working nicely without disturbing native behavior.
http://codepen.io/mjau-mjau/full/XbqBbp/
http://codepen.io/mjau-mjau/pen/XbqBbp
The only limitation is that transition will not be applied between seams in loop mode (for example when looping from last slide to slide 1). In examples I have used jQuery.
Essentially, it works by simply adding a CSS transition class to the .pswp__container on demand, but we need to add some javascript events to prevent the transition from interfering with Swipe, and only if mouseUsed. We also add a patch so the transition does not get added between loop seams.
1. Add the below to your CSS
It will be applied on-demand from javascript when required.
.pswp__container_transition {
-webkit-transition: -webkit-transform 333ms cubic-bezier(0.4, 0, 0.22, 1);
transition: transform 333ms cubic-bezier(0.4, 0, 0.22, 1);
}
2. Add javascript events to assist in assigning the transition class
This can go anywhere, but must be triggered after jQuery is loaded.
var mouseUsed = false;
$('body').on('mousedown', '.pswp__scroll-wrap', function(event) {
// On mousedown, temporarily remove the transition class in preparation for swipe. $(this).children('.pswp__container_transition').removeClass('pswp__container_transition');
}).on('mousedown', '.pswp__button--arrow--left, .pswp__button--arrow--right', function(event) {
// Exlude navigation arrows from the above event.
event.stopPropagation();
}).on('mousemove.detect', function(event) {
// Detect mouseUsed before as early as possible to feed PhotoSwipe
mouseUsed = true;
$('body').off('mousemove.detect');
});
3. Add beforeChange listener to re-assign transition class on photoswipe init
The below needs to be added in your PhotoSwipe init logic.
// Create your photoswipe gallery element as usual
gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, options);
// Transition Manager function (triggers only on mouseUsed)
function transitionManager() {
// Create var to store slide index
var currentSlide = options.index;
// Listen for photoswipe change event to re-apply transition class
gallery.listen('beforeChange', function() {
// Only apply transition class if difference between last and next slide is < 2
// If difference > 1, it means we are at the loop seam.
var transition = Math.abs(gallery.getCurrentIndex()-currentSlide) < 2;
// Apply transition class depending on above
$('.pswp__container').toggleClass('pswp__container_transition', transition);
// Update currentSlide
currentSlide = gallery.getCurrentIndex();
});
}
// Only apply transition manager functionality if mouse
if(mouseUsed) {
transitionManager();
} else {
gallery.listen('mouseUsed', function(){
mouseUsed = true;
transitionManager();
});
}
// init your gallery per usual
gallery.init();
You can just use a css transition:
.pswp__container{
transition:.3s ease-in-out all;
}
This might not be ideal for performance on mobile, but I just add this transition in a media query and allow users to use the swipe functionality on smaller screens.
I finally bit the bullet and spent some time making this work as nobody seemed to have a solution for that, not here neither on GitHub or anywhere else.
SOLUTION
I used the fact that a click on the arrow jumps to the next item and triggers the loading of the next image and sets the whole slide state to represent the correct situation in an instant.
So i just added custom buttons which would initiate a slide transition and then triggered a click on the original buttons (which i hid via CSS) which would update the slide state to represent the situation i created visually.
Added NEW next and prev arrows
Hid the ORIGINAL next and prev arrows via css
Animated the slide myself when the NEW next or prev arrows were clicked
Then triggered the click on the ORIGINAL next or prev arrows programmatically
So here is the code:
HTML
// THE NEW BUTTONS
<button class="NEW-button-left" title="Previous (arrow left)">PREV</button>
<button class="NEW-button-right" title="Next (arrow right)">NEXT</button>
// added right before this original lines of the example code
<button class="pswp__button pswp__button--arrow--left ...
CSS
pswp__button--arrow--left,
pswp__button--arrow--right {
display: none;
}
NEW-button-left,
NEW-button-right {
/* whatever you fancy */
}
JAVASCRIPT (helper functions)
var tx = 0; // current translation
var tdir = 0;
var slidepseactive = false;
// helper function to get current translate3d positions
// as per https://stackoverflow.com/a/7982594/826194
function getTransform(el) {
var results = $(el).css('-webkit-transform').match(/matrix(?:(3d)\(-{0,1}\d+(?:, -{0,1}\d+)*(?:, (-{0,1}\d+))(?:, (-{0,1}\d+))(?:, (-{0,1}\d+)), -{0,1}\d+\)|\(-{0,1}\d+(?:, -{0,1}\d+)*(?:, (-{0,1}\d+))(?:, (-{0,1}\d+))\))/)
if(!results) return [0, 0, 0];
if(results[1] == '3d') return results.slice(2,5);
results.push(0);
return results.slice(5, 8);
}
// set the translate x position of an element
function translate3dX($e, x) {
$e.css({
// TODO: depending on the browser we need one of those, for now just chrome
//'-webkit-transform': 'translate3d(' +String(x) + 'px, 0px, 0px)'
//, '-moz-transform': 'translate3d(' +String(x) + 'px, 0px, 0px)'
'transform': 'translate3d(' +String(x) + 'px, 0px, 0px)'
});
};
JAVASCRIPT (main)
// will slide to the left or to the right
function slidePS(direction) {
if (slidepseactive) // prevent interruptions
return;
tdir = -1;
if (direction == "left") {
tdir = 1;
}
// get the current slides transition position
var t = getTransform(".pswp__container");
tx = parseInt(t[0]);
// reset anim counter (you can use any property as anim counter)
$(".pswp__container").css("text-indent", "0px");
slidepseactive = true;
$(".pswp__container").animate(
{textIndent: 100},{
step: function (now, fx) {
// here 8.7 is the no. of pixels we move per animation step %
// so in this case we slide a total of 870px, depends on your setup
// you might want to use a percentage value, in this case it was
// a popup thats why it is a a fixed value per step
translate3dX($(this), tx + Math.round(8.7 * now * tdir));
},
duration: '300ms',
done: function () {
// now that we finished sliding trigger the original buttons so
// that the photoswipe state reflects the new situation
slidepseactive = false;
if (tdir == -1)
$(".pswp__button--arrow--right").trigger("click");
else
$(".pswp__button--arrow--left").trigger("click");
}
},
'linear');
}
// now activate our buttons
$(function(){
$(".NEW-button-left").click(function(){
slidePS("left");
});
$(".NEW-button-right").click(function(){
slidePS("right");
});
});
I used info from those SE answers:
jQuery animate a -webkit-transform
Get translate3d values of a div?
The PhotoSwipe can do this by itself when you use swipe gesture. So why not to use the internal code instead of something that doesn't work well?
With my solution everything works well, the arrow clicks, the cursor keys and even the loop back at the end and it doesn't break anything.
Simply edit the photoswipe.js file and replace the goTo function with this code:
goTo: function(index) {
var itemsDiff;
if (index == _currentItemIndex + 1) { //Next
itemsDiff = 1;
}
else { //Prev
itemsDiff = -1;
}
var itemChanged;
if(!_mainScrollAnimating) {
_currZoomedItemIndex = _currentItemIndex;
}
var nextCircle;
_currentItemIndex += itemsDiff;
if(_currentItemIndex < 0) {
_currentItemIndex = _options.loop ? _getNumItems()-1 : 0;
nextCircle = true;
} else if(_currentItemIndex >= _getNumItems()) {
_currentItemIndex = _options.loop ? 0 : _getNumItems()-1;
nextCircle = true;
}
if(!nextCircle || _options.loop) {
_indexDiff += itemsDiff;
_currPositionIndex -= itemsDiff;
itemChanged = true;
}
var animateToX = _slideSize.x * _currPositionIndex;
var animateToDist = Math.abs( animateToX - _mainScrollPos.x );
var finishAnimDuration = 333;
if(_currZoomedItemIndex === _currentItemIndex) {
itemChanged = false;
}
_mainScrollAnimating = true;
_shout('mainScrollAnimStart');
_animateProp('mainScroll', _mainScrollPos.x, animateToX, finishAnimDuration, framework.easing.cubic.out,
_moveMainScroll,
function() {
_stopAllAnimations();
_mainScrollAnimating = false;
_currZoomedItemIndex = -1;
if(itemChanged || _currZoomedItemIndex !== _currentItemIndex) {
self.updateCurrItem();
}
_shout('mainScrollAnimComplete');
}
);
if(itemChanged) {
self.updateCurrItem(true);
}
return itemChanged;
},

How to fadeIn element on page load instead of "appear"?

Im a really huge noob on jquery, I need to figure out how to change this code:
$('.social li').appear();
$(document.body).on('appear', '.social li', function(e, $affected) {
var fadeDelayAttr;
var fadeDelay;
$(this).each(function(){
if ($(this).data("delay")) {
fadeDelayAttr = $(this).data("delay")
fadeDelay = fadeDelayAttr;
} else {
fadeDelay = 0;
}
$(this).delay(fadeDelay).queue(function(){
$(this).addClass('animated').clearQueue();
});
})
});
to work in the way that it would start animation as soon as someone enters the landing page, right now it works good on everything besides IE10 and IE11, was told to change it to load by default not on "appear" but I tried document ready/load and I can't get it to work...
You could try fading all list items into view, each with a progessing 250ms delay:
$(window).load(function() {
$('.social li').hide().each(function(i) {
$(this).delay((i + 1) * 250).fadeIn(2000);
});
});
EDIT:
Using the same logic as your previous code to refactor, use the window.load method since the load event fires at the end of the document loading process. At this point, all of the objects in the document are in the DOM, and all the images and sub-frames etc have finished loading. So use this event to do the fading in animation of the list items into view, where their initial state will be hidden.
You have two variables declared fadeDelayAttr and fadeDelay but I noticed that only fadeDelay is being used, so fadeDelayAttr can be discarded. Also, this part of the code:
if ($(this).data("delay")) {
fadeDelayAttr = $(this).data("delay")
fadeDelay = fadeDelayAttr;
} else {
fadeDelay = 0;
}
can be simplified as the null-coalescing operator using a logical OR (||):
var fadeDelay = $(this).data("delay") || 0;
Since the fadeDelay variable gets its value from the data-delay attribute, this can then be passed in as an argument for the delay method and finally the refactored code will look like this:
$(window).load(function() {
$('.social li').hide().each(function() {
var fadeDelay = $(this).data("delay") || 0;
$(this).delay(fadeDelay).fadeIn(2000);
});
});
Working Demo

transitionend event fires twice

I have the following code and my problem is that the transitionend event is fired twice. I don't know what's causing this. I suspected the vendor prefixes caused it, but they don't. Even if I only leave transitionend and transition it will still fire twice.
CSS
transition: 1s ease-out;
JS
document.addEventListener('click', function (e) {
var submarine = document.querySelector('.submarine');
var submarineX = e.clientX - submarine.offsetWidth / 2;
var submarineY = e.clientY - submarine.offsetHeight / 2;
submarine.style.left = submarineX + "px";
submarine.style.top = submarineY + "px";
});
document.addEventListener('transitionend', function (event) {
console.log(event.type + " " + new Date().getTime());
});
Fiddle
document.addEventListener('transitionend', function (event) {
console.log(event.type + " " + new Date().getTime());
});
document.addEventListener('click', function (e) {
var submarine = document.querySelector('.submarine');
var submarineX = e.clientX - submarine.offsetWidth / 2;
var submarineY = e.clientY - submarine.offsetHeight / 2;
submarine.style.left = submarineX + "px";
submarine.style.top = submarineY + "px";
});
.submarine {
position: absolute;
top: 0;
left: 0;
width: 20px;
height: 20px;
background-color: red;
border-radius: 50%;
transition: 1s ease-out;
}
<div class="submarine"></div>
transitionend fires for each property transitioned, in your case top and left.
You can access the property associated with the event at event.propertyName.
There's no "transitionsend" event, so you will probably need some hackiness such as filtering the transitionend callback handling for only one of the transitioned properties. E.g.:
function (event) {
if (event.propertyName == 'top') {
//put your code here
}
});
ps. No browser fires the MSTransitionEnd event. It was at some point in the MS docs, but sometime before the IE10 beta release it was replaced by the standard transitionend event.
The event fires for each property that has been transitioned.
The propertyName way that Fabricio suggested is the proper way to do this, however if you are using jQuery you can also use one(); as well, like this.
$(document).one('transitionend webkitTransitionEnd MSTransitionEnd', function() {
...
});
For anyone looking for a simple, one time copy and paste solution (I've only included the necessary css). This doesn't answer the question and it does answer what I was looking for when I landed here.
CSS:
.my-elem {
transition: height 0.5s ease-out, opacity 0.5s ease-out;
}
JavaScript:
var elem = document.querySelector(".my-elem");
var transitionCounter = 0;
var transitionProp = window.getComputedStyle(elem , null)["transition-property"] || "";
// We just need to know how many transitions there are
var numTransitionProps = transitionProp.split(",").length;
elem.addEventListener("transitionend", (event) => {
// You could read event.propertyName to find out which transition was ended,
// but it's not necessary if you just want to know when they are all done.
if (transitionCounter < (numTransitionProps - 1)) {
transitionCounter++;
} else {
transitionCounter = 0; // reset
alert("I'm done!!!"); // do what you need to
}
}, false);
Tested in IE11, Chrome 48 and Firefox 37.
For anyone still looking for a more robust solution, like "allTransitionEnd" event, I've implemented a jQuery "special event", more as a proof of concept for something I was working on, but I might put out a lib on Github.
Check out the JSBin.
It's quite tricky, so I won't explain too much, but it makes it real easy to do stuff after ALL transitions have ended on an element:
$(function () {
$element.on('allTransitionEnd', function () {
// do something after all transitions end.
});
});
It works by probing the element for transition properties, then binds to the native transitionend events (vendor specific included) in order to keep track of properties that have finished transitioning. When all have finished transitioning it triggers any bound allTransitionsEnd handlers and then clears transition properties, in case they've changed as well, and probes for them fresh next time around.
This is really useful when multiple properties are being transitioned with varying delay and/or duration and you want to do something after all transitions have completed.
Example use cases:
Remove a flash message after fade-out and shrink.
Triggering "opened" and "closed" events in a reusable component, such as a menu or modal, where consumers may want to execute some logic after the transition has ended, without prying into css transitions.
If you are only transitioning one property, or have no varied delays and/or durations, then a simple solution works fine.
Works in latest version of Chrome, Firefox, Safari, Mobile Safari and IE11 and IE10. Doesn't work in IE8 because transitions are not supported. Bind to an additional native event as fallback.
You can use the target property to filter out events that are triggered by child elements and use propertyName to only trigger the event when a specific property changed.
const handleTransitionEnd = event => {
if (event.target !== myDomElementRef) return;
if (event.propertyName !== "height") return;
// Do my things
};
This is a relatively old question but I thought I'd share my answer:
function OnTransitionEvent() {
var t,
el = document.createElement('transitionElement');
var transitions = {
'transition' : 'transitionend',
'OTransition' : 'oTransitionEnd',
'MozTransition' : 'transitionend',
'WebkitTransition': 'webkitTransitionEnd'
};
for (t in transitions){
if (el.style[t] !== undefined){
return transitions[t];
}
}
}
var transitionEvent = OnTransitionEvent();
$(document).one(transitionEvent, function() {
console.log('done');
});

Categories

Resources