Dynamic slide height

Hi All,

Does anyone know of a way to dynamically resize the <ion-slide> height without jQuery and without previously knowing the heights?

So far, I’ve found that wrapping the whole <ion-slides> code within a <div> will allow my slides to expand to the size of the largest slide, but this means small slides have a lot of unused space at the bottom.

HTML Code

<div id="wrapper">
    <ion-slides (ionDidChange)="SlideChanged()">
        <ion-slide *ngFor="let page of _pages">
            <ion-item *ngFor="let item of page">
                <h3>{{ item }}</h3>
            </ion-item>
        </ion-slide>
    </ion-slides>
</div>

TS Code

export class HomePage {
public _pages: any[];

constructor(public navCtrl: NavController) {
    this._pages = [[],[],[]];           // 3 inner arrays
    var i;
    for(i = 1; i <= 5; i++)             // 1st element will have 5 items
        this._pages[0].push(i);
    for(i = 1; i <= 15; i++)             // 2nd element will have 15 items
        this._pages[1].push(i);
    for(i = 1; i <= 25; i++)             // 1st element will have 25 items
        this._pages[2].push(i);
}

SlideChanged() {
    console.log("slide changed");
}
}

Thank you!
Ryan

2 Likes

check out the api documentation, i think you are looking for autoHeight: http://idangero.us/swiper/api/#.V-HarSh96Hs

html:

<ion-slides [options]="sliderOptions">

.ts:

sliderOptions = { pager: true, autoHeight: true }

2 Likes

Hi @fishgrind,

I’ve tried that as well, and it still doesn’t work. =/

hmm, perhaps its because you are adding slides after it has initialised…

have you tried observer

observer | boolean | false

Set to true to enable Mutation Observer on Swiper and its elements. In this case Swiper will be updated (reinitialized) each time if you change its style (like hide/show) or modify its child elements (like adding/removing slides)

Observer didn’t kick off any new events unfortunately.

I’ve noticed that another thread mentions the autoHeight issue, but they didn’t find the solution either…

i guess i am out of suggestions… i’ll be following this post to find out too… good luck and sorry i couldnt help!

Thanks for trying.

So far, what I’ve done is written a function to toggle slide visibilities (visible for the active index, invisible for the rest).

ToggleSlideVisibility(index: number) {                  // only the index slide will be visible
    let numSlides = this._slides.length;
    console.log('slide count = ' + numSlides);

    for (let i = 0; i < numSlides; i++) {
        let htmlID = this._slides[i].htmlID;          // get the DOM element's ID
        let slide = document.getElementById(htmlID);

        if (i === index) {                              // make the i_th slide visible
            slide.style.display = 'inline';
        }
        else {                                          // make all other slides invisible
            //slide.style.visibility = 'hidden';        // "visibility: hidden" means the object is in its same place and size, just not rendered
            slide.style.display = 'none';               // "display: none" means the object is not a part of the DOM and size = 0
        }
    }
}

…but I’ll be working towards a complete solution since I’m not happy with using work-arounds. It works, but it looks odd since you’re swiping a blank slide into view, and only when it’s centered does it populate.

aight, last shot…

have you seen this: http://stackoverflow.com/questions/17513479/idangerous-swiper-issue-with-dynamic-content

note the solution is not the solution anymore but below that people claim to have solved it. It’s not exactly your problem but it could be a start…

@fishgrind here’s a hack that works manually, and it’s nice because of how short it is. Not yet sure how to automate it. Maybe you can provide some insight.

in your *.scss file, add the following classes (you don’t need to change any HTML; Swiper already relies on these classes):

.swiper-container {
    height: 1234px;
}

.swiper-wrapper {
    height: 1234px;
} 

I’ve tried dynamically editing the CSS via this discussion http://stackoverflow.com/questions/1409225/changing-a-css-rule-set-from-javascript …see Jorge Gonzalez’s “getCSSRule” function. But Chrome doesn’t force an update.

Kinda stuck at the moment…

Oooooh I solved it!

Ok, it’s intricate, but it works very well for my needs (ie: it may not work for everyone’s general needs). I went back to my original code, toggling the visibility of each slide. The change I made is that when you’re about to move to the next slide, you make the neighboring slides visible, too. After the swipe is complete, you hide everything but the current slide.

As @fishgrind suggested, you need to use sliderOptions, so your *.ts file should look something like this:

export class HomePage {
@ViewChild('mySlider') _slider: Slides;

public _sliderOptions: any;
public _payload: any[];    // NOTE: this is custom to my code that my slides are generated from in an ngFor loop -- it doesn't stand to help anyone else struggling with this problem

constructor(public _navCtrl: NavController, public http: Http) {
    let me = this;  // use a ref to ME in the block function, below
    
    this._sliderOptions = {
        pager: false,
        onTouchStart : function (){
            console.log('onTouchStart triggered...');

            let currentSlide = me._slider.getActiveIndex();
            console.log('  current slide = ' + currentSlide);

            let numSlides = me._payload.length;  // NOTE: I calculate this from an array of data & your code will likely not!!!!! you should use _slider.length()

            // change this range if you want to jump more than 1 slide away
            let lowerRange = Math.max(0, currentSlide - 1);
            let upperRange = Math.min(numSlides - 1, currentSlide + 1);

            console.log('[' + lowerRange + ', ' + upperRange + ']');

            me.SetVisibleSlideRange(lowerRange, upperRange);
        }
    }
}

Here is the SetVisibleSlideRange function:

SetVisibleSlideRange(i: number, j: number) {    // slides i through j will be visible, the rest will be invisible
    let numSlides = this._payload.length;
    console.log('slide count = ' + numSlides);

    for (let index = 0; index < numSlides; index++)
        if (index >= i && index <= j)
            this.SlideVisibility(index, true);
        else
            this.SlideVisibility(index, false);

}

// helper: only called from SetVisibleSlide and SetVisibleSlideRange
SlideVisibility(index: number, visible: boolean) {
    let htmlID = this._payload[index].htmlID;              // get the DOM element's ID
    let slide = document.getElementById(htmlID);

    if (visible)
            slide.style.display = 'inline';
    else {
            //slide.style.visibility = 'hidden';        // "visibility: hidden" means the object is in its same place and size, just not rendered
            slide.style.display = 'none';               // "display: none" means the object is not a part of the DOM and size = 0
        }
}

And over on the *.html side, your slider needs to take in the options you’ve just set up:

<ion-slides #mySlider (ionDidChange)="SlideChanged()" [options]="_sliderOptions" id="ionSlider">

I use SlideChanged to remove the visibility of the neighboring slides.

hey @ryanlogsdon, great way to solve it.

I am going to try the code out soon, but it looks good!

Glad you solved it!

This code isn’t work for me on ionic2 RC2. I solve that problem like this:

HTML:

<ion-slides #slider (ionWillChange)="slideWillChange()">
    <ion-slide *ngFor="let item of items; let itemIndex = index;" #slide>
        /// what you want
    </ion-slide>
</ion-slides>

TS:

@ViewChildren('slide') slides: QueryList<ElementRef>;
@ViewChild('slider') slider: Slides;

ionViewDidEnter() {
    this.slideWillChange();
}

slideWillChange() {
    this.slideElements = this.slides.toArray();
    let index = this.slider.getActiveIndex();

    this.slider._elementRef.nativeElement.style.height = this.slideElements[index].ele.children[0].clientHeight + 'px';
}

Hi @Vishin,

Can you share your updated code?

The slideWillChange() function does not compile in RC3. Specifically, this.slides.toArray(); cannot be turned into an array.

Thanks,
Ryan

Hi, I’ll share my code tomorrow when I try it on RC3, take a moment…

Hi All,

This will work in RC4:

HTML

<ion-slides #slider (ionDidChange)="SlideChanged()" [options]="_sliderOptions" id="ionSlider">
    <ion-slide #slide *ngFor="let robot of _robots">
        <ion-item *ngFor="let item of robot">
            <h3>{{ item }}</h3>
        </ion-item>
    </ion-slide>
</ion-slides>

Since there are a lot of things going on and extra packages needed, here’s the TS in its entirety:

TS

import { Component, ViewChild, ViewChildren, QueryList, ElementRef } from '@angular/core';
import { NavController, Slides } from 'ionic-angular';

@Component({
    selector: 'page-home',
    templateUrl: 'home.html'
})

export class HomePage {
    @ViewChildren('slide') slides: QueryList<ElementRef>;
    @ViewChild('slider') slider: Slides;

    public _robots: any[];
    public _sliderOptions: any;             // options for Ionic's native slider

    constructor(public navCtrl: NavController) {
        this._robots = [[],[],[]];           // 3 inner arrays
        var i;
        for(i = 1; i <= 5; i++)             // 1st element will have 5 items
            this._robots[0].push(i);
        for(i = 1; i <= 15; i++)             // 2nd element will have 15 items
            this._robots[1].push(i);
        for(i = 1; i <= 25; i++)             // 1st element will have 25 items
            this._robots[2].push(i);

        this._sliderOptions = {
            pager: false
        }
    }

    ionViewDidEnter() {
        this.SlideChanged();
    }

    SlideChanged() {
        let currentSlide = this.slider.getActiveIndex();
        let slideElements = this.slides.toArray();

        let targetSlide = slideElements[currentSlide];
        //console.log(targetSlide);
        let height = targetSlide['ele']['children'][0]['clientHeight'];
        //console.log('target slide height = ' + height);

        this.slider._elementRef.nativeElement.style.height = height + 'px';
    }
}

Many thanks to @Vishin!

2 Likes

Here is the RC5 solution.

HTML

    <div id="wrapper">
        <ion-slides #slider>
            <ion-slide *ngFor="let robot of _robots">
                your robots here
            </ion-slide>
        </ion-slides>
    </div>

You wil need the div wrapper for when the slides take up more than the length of the screen.

TS

  1. import ViewChild from @angular/core

  2. add this line to your class (anywhere is fine)

    @ViewChild('slider') slider: Slides;

  3. add this function to the class

     ngAfterViewInit() {
         this.slider.autoHeight = true;
     }
    

Thank you, Ionic Team, for making so many changes that the solution is this simple now!

4 Likes

I’ve not gotten this to work in my case - I don’t create the slider until the user selects an ion segment button.

Once the user selects this tab, this makes an api call and ngIf creates the slider underneath the tab.

Playing around with it, I’ve not gotten this.slider.autoHeight = true to adjust the slide height. Any thoughts on what I should try?

Remove the ngIf from the equation.

Try hard-coding a slider in place. Does it work? If the answer is still no, we know that the ngIf in not the problem.

I am at RC6. In my case if I load a 400x300 png sometimes it centers the images sometimes it does not?
If I load larger size images they may take the whole width of the available view area and thus no auto-height is taking place. I tried the div wrapper instead of , heh whatever you gurus say. Also tried, the sizing code you adjusted from @Vishin but I does not seem to work. The observer thingy does not seem to be available in ionic2 implementation, why not?

Here is the HTML

    <!--<section>-->
        <div id="wrapper">
        <ion-slides  #mySlider (ionSlideDidChange)="SlideChanged()">
            <ion-slide  #slides *ngFor="let s of Slides ;  let ii=index" class="AMSImage">
                <h1>Slide {{ii+1}} of {{AMSController.AMSData.Count}}</h1>
                <div id="AMSContent">
                    <img id="AMSImage" [src]="AMSController.GetImage(s)">

                    <div id="AMScontainer">
                        <div id="AMStemp">
                            <div id="AMStext" class=AMSText>
                                {{AMSController.AMSData.TextOfSlide(s)[0]}}
                            </div>
                        </div>
                    </div>
                </div>
                <div class="swiper-button-prev"></div>
                <div class="swiper-button-next"></div>
            </ion-slide>
            <!-- If we need navigation buttons -->
        </ion-slides>
        </div>
    <!--</section>-->

Here are the input props, (it took me a while to figure out what “input” meant, never heard such a thing)

 ngAfterViewInit() {
        console.log("Slider------------>>Input Props Set");
        // http://ionicframework.com/docs/v2/api/components/slides/Slides/
        // http://idangero.us/swiper/api/#.V6zJbPmU3RY for API, options, ...
        // 20160801 created
        // 20170202 breaking change -> changed to props at RC6
        this.slider.initialSlide = 0;
        this.slider.loop = false;
        this.slider.pager = true;
        this.slider.speed = 500;
        this.slider.centeredSlides = true;
        // this.slider.autoHeight = true;
        // this.slider.observer = true ; // not vailable
        // autoplay = 1000
        // effect = "cube"
    }

more code

// NOTE: this does NOT fire if the slider is already at the requested slide.
    // 20160808
    SlideChanged() {
        try {
            // this.AdjustHeight();
            var index = this.slider.getActiveIndex();
            console.log("slide CHANGED", index);
            // this.AudioPlayList.Play(this.AMSController.AMSData.Audio(index));
            this.AMSController.PlayIndex(index + 1);
        }
        catch (e) {
            console.log(<Error>e.message);
        }
    }

    // 20170203
    // https://forum.ionicframework.com/t/dynamic-slide-height/63891/15
    @ViewChildren('slides') slides: QueryList<ElementRef>;
    AdjustHeight(): void {
        let currentSlide = this.slider.getActiveIndex();
        let slideElements = this.slides.toArray();

        let targetSlide = slideElements[currentSlide];
        //console.log(targetSlide);
        let height = targetSlide['ele']['children'][0]['clientHeight'];
        //console.log('target slide height = ' + height);

        this.slider._elementRef.nativeElement.style.height = height + 'px';
    }

Is too much to expect the slider to auto-size? should I consider writing code to adjust height in landscape mode and width in portrait mode?

here is another snapshot in chrome, if the client area is small or I load large images like 1920x1080


What am I doing wrong?
Thank you.

Upgrade to Ionic v2.0.0 and report back with your issues.

There have been a lot of modifications to the back-end with respect to Swiper.js in v2.0.0.