ionic-team / ionic-framework

A powerful cross-platform UI toolkit for building native-quality iOS, Android, and Progressive Web Apps with HTML, CSS, and JavaScript.
https://ionicframework.com
MIT License
51.1k stars 13.51k forks source link

Ion Slides not functioning after navigating back to them #10101

Closed chrisgriffith closed 5 years ago

chrisgriffith commented 7 years ago

Ionic version: (check one with "x") [ ] 1.x [ X] 2.x

I'm submitting a ... (check one with "x") [X ] bug report [ ] feature request [ ] support request => Please do not submit support requests here, use one of these channels: https://forum.ionicframework.com/ or http://ionicworldwide.herokuapp.com/

Current behavior: The home page has a simple 3 slide slider. The button on each slide will change the page to a details screen. If you navigate back to the home screen using the header navigation, the slider will function again. If you use the ion button to go back (via this.navCtrl.push(HomePage), the slider will NOT function. This only appears when running the app in the local browser NOT in device mode. In device mode the slider will function correctly. In my advanced case, I am able to jump to a specific slide, but no swipe interaction is possible.

Expected behavior: The Ion Slider would accept the ability to slide under user control

Steps to reproduce: Verify the base ion slide is functioning Navigate to another page. Navigate back to the page with the slider via navCtrl.push() Verify the ion slide is NOT functioning

Related code: Here is the sample repo: https://github.com/chrisgriffith/ionic-slider-bug

Other information: No errors are listed in the console. Brandy has reproduced the issue.

jerrydeng commented 7 years ago

I have similar issue when trying to navCtrl.push another page that contains another slider, all subsequent push have issues on local browser. On device or emulator, they worked as expected.

DeeM297 commented 7 years ago

It' funny cause I am having this problem when switching tabs but only on Android device and not on browser with ionic serve

I've posted the details on the forum

chandanch commented 7 years ago

I'm still facing this issue in 2.2.1, Refer to this plunker: http://plnkr.co/edit/RjnMlZ?p=preview

ghost commented 7 years ago

I'm also seeing this behaviour. When I remove the following code, it seems to work fine:

<div *ngFor="let displayField of (survey.displayFields | imageDisplayFieldsOnly)">
  <img [src]="getImageUrl(dataItem.value[displayField.dataField])" />
</div>

Could it be that there is an exception being thrown by the code above the second time around which breaks things deeper down the stack?

When I swap out the src on that tag with a static reference to an image on the web, it works fine. Hope that narrows the issue down.

UPDATE:

A workaround for my specific issue has been to add a *ngIf to the image element so it is hidden until the value is available.

mj6uc commented 7 years ago

12166 -- Looks similar issue

kpoconnor commented 7 years ago

So the short explanation of this bug is that it is deeply ingrained in both how Swiper (the slides library that Ionic is using) and how Ionic’s navigation works.

When you push() a page onto the navigation stack, the previous page still exists in the DOM. Ionic applies the following styles to the previous page: display: none, visibility: hidden, opacity: 0, as well as setting the previous page’s z-index to be place it behind the newly pushed page. The most important one here is display: none, since this prevents the previous page from taking up any space in the DOM.

Now, if the previous page has slides, and the current page rotates, the slides are still listening for this rotation event and will initiate a resize. This resize also includes repositioning slides on the previous page.

Swiper dictates the position of the slides based on the size of both the slides and the container. When the page and all its children are set to display: none, the slides and their container have no height or width values, so when the slides resize, the new position is incorrect.

Since popping the current page off of the nav stack and returning to the previous page doesn’t trigger the slides to resize or update in any way, this results in the slides being misaligned when returning to the slides page.

The simplest solution for this bug would be to have Ionic automatically trigger Swiper's onResize() event when navigating back to a page with slides, this wouldn’t require much or any change to the underlying Swiper code, probably just exposing the onResize() function that swiper-event.js contains. Even better would be for the slides to unsubscribe to their event listeners when navigating away too.

I’ve developed a workaround though, that would only involve a one-line code change in the Ionic source code.

Ionic exposes an update() function for their slides that can be called manually. Documentation here.

/**
 * Update the underlying slider implementation. Call this if you've added or removed
 * child slides.
 * @param {?=} debounce
 * @return {?}
 */
Slides.prototype.update = function (debounce) {
    var _this = this;
    if (debounce === void 0) { debounce = 300; }
    if (this._init) {
        this._plt.cancelTimeout(this._tmr);
        this._tmr = this._plt.timeout(function () {
            update(_this, _this._plt);
            // Don't allow pager to show with > 10 slides
            if (_this.length() > 10) {
                _this.paginationType = undefined;
            }
        }, debounce);
    }
};  

This update function calls the Swiper update() function: update(_this, _this._plt);

However, looking at the Swiper update function indicates that this update function is meant to take 3 variables:

/**
 * @param {?} s
 * @param {?} plt
 * @param {?=} updateTranslate
 * @return {?}
 */
export function update(s, plt, updateTranslate) {   ...

This means that when you manually call this.slides.update() on your page, the position of the slides doesn’t actually change, since Ionic’s update() function doesn’t pass through this updateTranslate boolean to indicate that the translation of the slides should update as well.

If I am manually calling update on Ionic slides, I would expect that it would also recalculate the positioning of the slides, and would change the update call within this.slides.update() to be update(_this, _this._plt, true);.

With these changes to the Ionic source code, users that are experiencing this issue can call:

public ionViewWillEnter() {
    this.slides.update();
}

to get rid of the symptoms that they are experiencing as part of this bug.

Additional Info:

After digging into the Swiper code within Ionic, I can provide some background info on this issue. Swiper doesn’t have a sense of ‘rotation’ per se, Ionic has provided an ‘onResize’ event within their ‘swiper-events.js’, which is triggered on rotation or when the browser size changes. (You can take a look at the Swiper resize handler code, in swiper-events.js)

Resizing invokes Swiper’s updateContainerSize, updateSlideSize, and, if you are not using ‘freeMode’, the slideTo functions. These functions all illuminate very specific issues about this bug. All three of these functions live in the main ‘swiper.js’ file.

updateContainerSize() sets the s._renderedSize variable that is used in later calculations. s._renderedSize is based on the width or height of the swiper DOM element, or its containing element. Assuming that you let Swiper dictate its own size based on the items that are contained within a slide, s._renderedSize will always be based on its container.clientWidth or container.clientHeight.

updateSlideSize() sets the contents of the s._snapGrid array (lines 449-468). These lines iterate over all the slides and stores off their position. The slide position is based on the size of the slide, as determined by either s._renderedSize or the left and right margins of the slide in the DOM, the offset applied to the slides, and the space between slides. Basically, if the slides or their container have no width/height, their positions in the s._snapGrid array are all incorrect.

slideTo(), uses s._snapGrid to translate the .swiper-wrapper element on the screen. It does this by pulling the value from the s._snapGrid for the slideIndex it is translating to, and then setting the .swiper-wrapper’s transform: translate3d(….); based on this value.

On the Ionic side, Ionic’s navCtrl object holds a list of _views, each of these _views has a _isHidden variable that is set within a function called _domShow(). This function is the one that applies the [hidden] class to the page that is navigated from. This [hidden] class is what applies display: none !important;.

Also, the event subscriptions are applied to the Ionic Slides in the _initSlides() function, stored in the _unregs array, the Swiper events are dictated by Swipers initEvents() function, which is called by _initSlides().

See also: #11216

sagarkhan commented 6 years ago

I think its late for a solution but it might help someone... I found a quick workaround for this issue after reading @kpoconnor comment, it was a thorough analysis of the problem i must say. Now the workaround which i did is quite funny but works.

//Stop the slide autoplay when the view will leave
public ionViewWillLeave(){  
 this.slide.stopAutoplay();
}

//Start the slide autoplay when the view will enter
public ionViewWillEnter(){
 this.slide.startAutoplay();
}

I don't know if this is a acceptable solution or not but as long as it works it's great for me... #Cheers

chandanch commented 6 years ago

12213

AlGantori commented 6 years ago

Slider will not autoplay when not in View (when you navigate temporarily to another page) Here is a plunker I put together https://plnkr.co/edit/DPFVFYkW4cZbcYAE0AtI

more details here... https://github.com/ionic-team/ionic/issues/14391

m1nka commented 6 years ago

Has anybody found a workaround or fix for this? @sagarkhan solution does not work for me, since im using slides in a custom child component.

sagarkhan commented 6 years ago

@m1nka if you are using slides in a child component can you try passing an event to the child component from parent's component ionViewWillLeave() and ionViewWillEnter(). On receiving that event, the child component will start and stop autoplay accordingly?

ionitron-bot[bot] commented 5 years ago

This issue has been automatically identified as an Ionic 3 issue. We recently moved Ionic 3 to its own repository. I am moving this issue to the repository for Ionic 3. Please track this issue over there.

If I've made a mistake, and if this issue is still relevant to Ionic 4, please let the Ionic Framework team know!

Thank you for using Ionic!

ionitron-bot[bot] commented 5 years ago

Issue moved to: https://github.com/ionic-team/ionic-v3/issues/165