I just got a really unexpected bug in my sveltekit application and I can't find anything online talking about it
I have a normal sveltekit application but instead of hydrating the new code when navigating to a new page, it just adds the new code on top of the old one, when i refresh the page it removes the old code (from the previous page)
Edit: after a little bit more exploring I realized it only happens on one page, what part of my code could make this happen?
I had the same issue when having a page loading indicator on SvelteKit for internal navigating. Any page DOM modification, to display the loading indicator during the page navigating, caused the paged appearing twice error. The workaround was to not modify the page during the navigating and use only CSS to display, animate and hide the loading indicator.
Here is my loading indicator code and Here is my documentation regarding the issue. I am not sure if this an internal bug in SvelteKit, so I did not file any bug reports, as I do not have a clean repeatable example. You can also see the fixed page loading indicator on the action on this page if you click any of the blockchains.
<script>
/**
* Svelte does not give a load indication if you hit a link that leads to a page with slow load() function.
* Svelte uses internal router, not server-side loading.
* Thus, we need to manually give some indication in the user interface if the loading takes more than a blink of an eye.
*
* The component is originally made for https://tradingstrategy.ai
*
* Based on the original implementation https://github.com/shajidhasan/sveltekit-page-progress-demo by Shajid Hasan.
*
* As this component is absolutely position, you can put it at any part of your __layout.svelte.
*/
import { onDestroy, onMount } from 'svelte';
import { writable } from 'svelte/store';
import { tweened } from 'svelte/motion';
import { cubicOut } from 'svelte/easing';
const navigationState = writable();
const progress = tweened(0, {
duration: 3500,
easing: cubicOut
});
const unsubscribe = navigationState.subscribe((state) => {
// You will always get state=undefined
// event on the server-side rendering, so
// safely ignore it
//console.log("The loading state is", state);
if (state === 'loading-with-progress-bar') {
progress.set(0, { duration: 0 });
progress.set(0.8, { duration: 5000 });
} else if (state === 'loaded') {
progress.set(1, { duration: 1000 });
}
});
onMount(() => {
// progress.set(0.7);
});
onDestroy(() => {
unsubscribe();
});
</script>
<!-- See the (little) documentation of special SvelteKit events here https://kit.svelte.dev/docs#events -->
<svelte:window
on:sveltekit:navigation-start={() => {
// If the page loads fast enough in the preloading state,
// never display the progress bar
$navigationState = 'preloading';
// Delay the progress bar to become visible an eyeblink... only show if the page load takes too long
setTimeout(function() {
// After 250ms switch preloading to loading-with-progress-bar
if($navigationState === 'preloading') {
$navigationState = 'loading-with-progress-bar';
}
}, 500);
}}
on:sveltekit:navigation-end={() => {
$navigationState = 'loaded';
}}
/>
<!--
Make sure the container component is always in the DOM structure.
If we make changes to the page structure during the navigation, we get a page double render error:
https://stackoverflow.com/questions/70051025/sveltekit-adds-new-page-on-top-of-old-one
Not sure if this is a bug or a feature.
Thus, make sure any progress animation is done using CSS only.
-->
<div class="page-progress-bar" class:loaded={$navigationState === 'loaded'} class:preloading={$navigationState === 'preloading'} class:loading={$navigationState === 'loading-with-progress-bar'}>
<div class="progress-sliver" style={`--width: ${$progress * 100}%`} />
</div>
<style>
/* Always stay fixed at the top, but stay transparent if no activity is going on */
.page-progress-bar {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 0.5rem;
background: transparent;
z-index: 100;
opacity: 0;
transition: opacity 0.5s;
}
/* After transitioning from preloading to loading state, make the progress bar visible with CSS transition on opacity */
.page-progress-bar.loading {
opacity: 1;
transition: opacity 0.5s;
}
.progress-sliver {
width: var(--width);
background-color: var(--price-up-green);
height: 100%;
}
</style>
I also saw this issue with the transition directive. The new page loads while the exit animation is playing. I used local transitions to solve this.
https://svelte.dev/tutorial/local-transitions
Related
I want to create a smooth transition between 2 images with a legend. The images come from an object-array of images.
Because works only on single tags and components, I've created a component to define the image+legend.
<transition>
<home-image :slide="slide" :key="slide"></home-image>
</transition>
The classes I define are like this
.v-enter-active,
.v-leave-active {
transition: opacity 2s ease-in-out;
}
.v-leave,
.v-enter-to {
opacity: 1;
}
.v-enter,
.v-leave-to {
opacity: 0;
}
The new image is returned by a method
updateSlide() {
this.slide = this.entries[ Math.floor( Math.random() * this.entries.length ) ];
}
where entries is my array defined in data
this.slide is updated in regular intervals, every 10seconds like this, which is defined in the created() section
this.updateSlide();
this.uSlide = setInterval( this.updateSlide, 10000);
The code works, in the sense that a new image is loaded in this.slide every 10 seconds. However, the transitions work only "half-way".
There is no transition fading out: the "old image" disappears and makes way for the new image fading in.
However, what I'd like is a smooth transition from one to the other.
I've tried more than a couple of ideas including using mode="out-in" and "in-out" but nothing works as I want.
What am I overlooking?
Found out position in v-enter and v-leave had to be set.
Code is now:
.v-leave,
.v-enter-to {
position: relative;
opacity: 1;
}
.v-enter,
.v-leave-to {
position: absolute;
opacity: 0;
}
The Situation
I have a fixed nav bar at the top of the page. As you scroll down through different sections of the page the nav bar dynamically updates (underlines and highlights). You can also click a section on the nav bar and it will scroll down to that section.
This is done using the intersection observer API to detect which section it's on and scrollIntoView to scroll to each section.
The Problem
Lets say you are on section 1 and you click the last section, 5, and it scrolls the page down past all the other sections in-between. The scroll is fast and as it scrolls all the sections are detected by the intersection observer and therefore the nav is updated. You end up getting an effect of the nav quickly changing for each nav item as it goes past each corresponding section.
The Goal
How do you delay the intersection observer from triggering the menu change if the section is only in frame for a millisecond? When quickly scrolling the nav bar should only update once the scrolling has stopped on a section.
Code Setup
const sectionItemOptions = {
threshold: 0.7,
};
const sectionItemObserver = new IntersectionObserver((entries, observer) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
// select navigation link corresponding to section
} else {
// deselect navigation link corresponding to section
}
});
}, sectionItemOptions);
// start observing all sections on page
sections.forEach((section) => {
sectionItemObserver.observe(section);
});
Ideas
My first thought was to put a setTimeout so that the nav wouldn't change until the Timeout was finished, then cancel the Timeout if the section left the screen before the timeout finished. But as the timeout is in a forEach loop this didn't work.
const sectionItemObserver = new IntersectionObserver((entries, observer) => {
entries.forEach((entry) => {
let selectNavTimeout
if (entry.isIntersecting) {
// Set timeout when section is scrolled past
selectNavTimeout = setTimeout(() => {
// select navigation link corresponding to section
}, 1000)
} else {
// deselect navigation link corresponding to section
// cancel timeout when section has left screen
clearTimeout(selectNavTimeout)
}
});
}, sectionItemOptions);
Any other ideas would be greatly appreciated! Thanks :)
I had the same problem. I end up use the setTimeout approach. You need to associate the timeouts with the entry target, provided each entry target has some unique ID. For example, suppose we are intersecting nodes with id property:
let timeouts = {};
const observer = new IntersectionObserver((entries, ob) => {
for (const e of entries) {
if (e.isIntersecting) {
timeouts[e.target.id] = setTimeout(() => {
ob.unobserve(e.target)
// handling
}, 1000) // delay for 1 second
} else {
clearTimeout(timeouts[e.target.id])
}
}
}, options)
Ran into same issue. Per this article: https://web.dev/intersectionobserver-v2/, observer v2 allows you to set a delay in the observer options. In my nav menu situation the delay works like a charm:
const observer = new IntersectionObserver((changes) => {
for (const change of changes) {
// ⚠️ Feature detection
if (typeof change.isVisible === 'undefined') {
// The browser doesn't support Intersection Observer v2, falling back to v1 behavior.
change.isVisible = true;
}
if (change.isIntersecting && change.isVisible) {
visibleSince = change.time;
} else {
visibleSince = 0;
}
}
}, {
threshold: [1.0],
// 🆕 Track the actual visibility of the element
trackVisibility: true,
// 🆕 =====ANSWER=====: Set a minimum delay between notifications
delay: 100
}));
After lots of brainstorming I came up with an idea that didn't exactly answer the question of delaying the Intersection Observer API but it did solve the problem of the nav bar flickering.
The highlighting of the nav item is done through adding an "is-active" class onto it and then applying CSS to it. Because the "is-active" class is only on the nav item for a split second you can use CSS keyframes to delay the application of CSS styles. By the time the delay has finished the "is-active" class isn't present on the nav item and no styles are changed.
Keeping the original JS the same this is the CSS used
.is-active {
animation: navItemSelected;
animation-duration: 0.3s;
// delay longer than the time nav item is in frame
animation-delay: 0.1s;
// fill mode to hold animation at the end
animation-fill-mode: forwards;
}
#keyframes navItemSelected {
// default non-selected style of nav item
from {
font-style: normal;
opacity: 0.5;
}
// highlighted style of nav item
to {
font-style: italic;
opacity: 1;
}
}
I am coding a SOS page, when I click the button that I want it showing a popup page like this. Then user can choose phone number.
sos.html page
<button ion-button color="light" (click)="openSosPop()">Call</button>
sos.ts page
openSosPop() {
this.openModal('SosPopPage');
// let contactModal = this.modalCtrl.create(SosPopPage);
// contactModal.present();
}
openModal(pageName) {
this.modalCtrl.create(pageName, null, { cssClass: 'inset-modal' })
.present();
}
sos.css page
ion-modal.inset-modal {
// transparent black background overlay
background-color: rgba(0, 0, 0, .5) !important;
transition: opacity .25s ease-in-out;
padding: 20vh 10vw;
}
I am not using lazy loading
If you are not using lazy loading, it is not an IonicPage.
Adding IonicPage decorator allows you to use string names to handle pages.
This will automatically create a link to the MyPage component using the same name as the class, name: 'MyPage'. The page can now be navigated to by using this name.
Since you are not lazy loading, you cannot use a string to navigate or create modals and popups. You have to use the actual page/component.
Import the page.
import <path_to_page>;
this.openModal(SosPopPage);//create the modal.
How to read dimensions and move a div that is hidden before Vue transition starts? For example, a user clicks a button and I want to move a hidden div to appear under the button with a fade-in transition. I need to be able to both read the dimensions and move the top/left position of the hidden div before the transition starts.
Let's say I'm using v-show="active" on the div, where active is my reactive data property I want to set to true and be able to move the div before transition starts.
I've tried all these:
Move the div first, then on nextTick set active = true.
Use the javascript hook beforeEnter to try to move the div before transitions start.
Use the javascript hook enter (and 'done' callback) to try to move the div before transition starts.
Tried all the above with updating the DOM immediately with the new position before setting active = true. (In other words, not via data binding, but actually setting element style properties directly like this.$refs.content.style.top = '500px' to avoid any waiting on the virtual DOM.) However, ideally I would like to accomplish this without directly touching the DOM, but using nextTicks instead. Both approaches fail.
Tried with some success with a hacky transition: all .8ms ease-in, top 1ms, left 1ms.
Tried with success with moving the div first then setting active in a setTimeout. This is not the right solution though.
Update
Thanks to the accepted answer I was able to see that I can read dimensions on nextTick (by which time v-show has turned on display). However, it turns out I needed the transition to be all transition all .3s and that would cause the movement to be included. The DOM will gather up all the changes and apply them together, which means they get lumped into the transition that is later added by Vue. The solution ended up being that I needed to make the movements, then trigger the DOM to repaint first, then trigger the v-show to turn on. Here's an example method:
startTransition () {
this.$refs.content.offsetHeight // <-- Force DOM to repaint first.
this.isContentActive = true // <-- Turns on v-show.
},
Use v-bind:style to move your window and it all works as intended.
Update: To check the size of the popup itself, it has to be shown, so I'm using v-show instead of v-if. The first thing I do is make it visible; on the next tick, I can measure it and place it.
new Vue({
el: '.container',
data: {
top: 0,
left: 0,
width: 0,
show: false
},
methods: {
showFloater: function(evt) {
const t = evt.target;
this.show = true;
Vue.nextTick(() => {
const fEl = this.$el.querySelector('.floating');
this.top = t.offsetTop + 30;
this.left = t.offsetLeft;
this.width = fEl.offsetWidth;
setTimeout(() => this.show = false, 1000);
});
}
}
});
.container {
position: relative;
}
.floating {
border: thin solid black;
padding: 3em;
position: absolute;
}
.fade-enter-active, .fade-leave-active {
transition: opacity .5s
}
.fade-enter, .fade-leave-to /* .fade-leave-active in <2.1.8 */ {
opacity: 0
}
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.2.1/vue.js"></script>
<div class="container">
<button #click="showFloater">Could go here</button>
<button #click="showFloater">Or here</button>
<transition name="fade">
<div v-show="show" class="floating" v-bind:style="{
top: top + 'px',
left: left + 'px'
}">
This window is {{width}}px wide.
</div>
</transition>
</div>
I'm using Ember and Liquid Fire to create some interesting material-design-inspired animated transitions between routes.
I've created two transitions. The first occurs during the transition from the index route to the about route and uses the explode, flyTo, and toLeft transitions successfully. Here I match by a data-nav attribute on both routes to create a smooth, seamless transition that makes the matched element appear to move across the screen to the next page as it flies left. Thumbs up!
The second occurs during the transition from the about route back to the index route. Here I match a different element than above by a data-text attribute on both routes, however unlike the example above, the elements are not identical. Specifically, the font-size is different. Unfortunately this has an undesired effect of immediately displaying the larger font-size text and then having it fly across the page.
What I'd like to do is add an additional transition that animates the font-size. Sounds simple enough.
Below is my initial transitions.js file with the two transitions described above.
export default function() {
var duration = 1000;
this.transition(
// --------------------- INDEX to ABOUT ----------------------------------- //
this.fromRoute('index'),
this.toRoute('about'),
this.use('explode', {
matchBy: 'data-nav',
use: [ 'flyTo', { duration } ]
}, {
use: [ 'toLeft', { duration } ]
}),
// --------------------- ABOUT to INDEX ----------------------------------- //
this.reverse('explode', {
matchBy: 'data-text',
use: [ 'flyTo', { duration } ]
}, {
use: [ 'toRight', { duration } ]
})
);
}
I believe the answer is to create my own custom transition, however it is turning out to be rather challenging. I created a custom transition called scale-font.js and added it to my transitions.js file for this transition.
import { animate, Promise } from "liquid-fire";
export default function scaleFont(opts={}) {
// returns a Promise that resolves when the transition is done
if (!this.newElement) {
return Promise.resolve();
} else if (!this.oldElement) {
this.newElement.css({visibility: ''});
return Promise.resolve();
}
var oldFontSize = this.oldElement.css('font-size');
var newFontSize = (opts.selector ? this.newElement.find(opts.selector) : this.newElement).css('font-size');
this.newElement.css({ visibility: 'hidden' });
return animate(
this.oldElement,
{ fontSize: oldFontSize },
{ duration: 1000 }
).then(() => {
return animate(
this.newElement,
{ fontSize: newFontSize },
{ duration: 1000, visibility: 'visible' }
);
});
}
Unfortunately this doesn't quite work. The first problem is that the oldFontSize is not correct. It ends up grabbing the new font size. The second problem is no font scaling occurs.
I've researched animating fonts, so I am fairly certain it can be done. Suggestions?