0

I have a field image with an array of image objects.

User can upload some pictures in this field.

In the fluid template it looks like:

<f:if condition="{field.image}">
    <f:then>
        <f:for each="{field.image}" as="image" iteration="iterator">
            <f:image src="file:{image.properties.uid}" alt="" class="img-fluid" width="1250c" height="600c" />
        </f:for>
    </f:then>
</f:if>

In case there are three pictures uploaded, all will be displayed. But I need to show only one random picture in this place. How can I do it?

biesior
  • 55,576
  • 10
  • 125
  • 182
astoun
  • 11
  • 2
  • `{field.image}` is a collection (array if I remember well). Create custom ViewHelper to select one image randomly. The logic of VH should be rather easy. – biesior Aug 10 '20 at 15:57
  • 1
    Note that approach with custom ViewHelper will be cached so it will display the same image until he next cache clearing. You can anyway create a ViewHelper which will return ie. array of possible images as JSON object so you will be able to randomly select one of them without clering cache. – biesior Aug 10 '20 at 16:39
  • Maybe this helps: http://www.typo3-probleme.de/2019/09/12/typo3-for-each-schleife-random-sortierung-ausgeben-2369/ – exotec Aug 10 '20 at 17:23

4 Answers4

1

As mentioned in my comment you can create your own ViewHelper, anyway, the fast workaround is using jQuery just in code. First, hide all images by default and then show one randomly.

<f:if condition="{field.image}">
    <f:then>
        <f:for each="{field.image}" as="image" iteration="iterator">
            <f:image src="file:{image.properties.uid}" alt="" class="img-fluid random-image" style="display: none" width="1250c" height="600c" />
        </f:for>
        <script>

            $("img").not(":visible").each(function () {
                $(this).data("src", this.src);
                this.src = "";
            });

            shuffle($(".random-image")).slice(0, 1).each(function () {
                $(this).attr('src', $(this).data('src')).show();
            });

            // shuffle function thanks to Jon: 
            // https://stackoverflow.com/a/11186765/1066240
            function shuffle(array) {
                let m = array.length, t, i;
                while (m) {
                    i = Math.floor(Math.random() * m--);
                    t = array[m];
                    array[m] = array[i];
                    array[i] = t;
                }
                return array;
            }
        </script>
</f:if>

Note this approach should be considered as dirty-hack and can be done better i.e. for avoiding loading images which will not be shown ever (on this page refresh). also keep in mind that you can display i.e. 3 random images using slice(0, 3), however, you need to make sure if you have at least 3 images.

biesior
  • 55,576
  • 10
  • 125
  • 182
0

Thanks for comments and answers but this solution isn't good for my case because of cache.

The jQuery solution is also not good for me because all pictures should be loaded first. There can be i.e. 10 background pictures with 200 kb and instead of only loading 200 kb, 2 mb were loaded to show only one picture. There may also be a problem with the browser cache.

I have found another solution without own ViewHelper, jQuery and cache problem.

Here is it:

<f:if condition="{field.image}">
    <f:then>
        {f:count(subject: field.image) -> f:variable(name: 'maximage')}
        {v:random.number(minimum: 1, maximum: maximage) -> f:variable(name: 'randomimage')}
        <f:for each="{field.image}" as="image" iteration="iterator">
            <f:if condition="{iterator.cycle} == {randomimage}">
                <f:image src="file:{image.properties.uid}" alt="" class="img-fluid" width="1250c" height="600c" />
            </f:if>
        </f:for>
    </f:then>
</f:if>

Explanation:

{f:count(subject: field.image) -> f:variable(name: 'maximage')} – get the size of the image collection and put it in the new variable maximage.

{v:random.number(minimum: 1, maximum: maximage) -> f:variable(name: 'randomimage')} – get the random number between 1 and maximage and put it in the new variable randomimage.

<f:for each="{field.image}" as="image" iteration="iterator"> - start the normal loop for field.images

<f:if condition="{iterator.cycle} == {randomimage}"> - condition for show the image – show it only if the current iterator {iterator.cycle} is equal to the {randomimage}.

Have tested in Typo3 8.7.20. Hope that will also work in Typo3 9.x and 10.x.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
astoun
  • 11
  • 2
0

Since you're already using VHS:

Use v:iterator.random - https://viewhelpers.fluidtypo3.org/fluidtypo3/vhs/5.0.1/Iterator/Random.html

But make sure you are aware of the "result gets cached" pitfall described by @biesior above (How to show a random image from image array in TYPO3 Fluid Template?).

Claus Due
  • 4,166
  • 11
  • 23
0

I made a solution with several benefits:

  • works with cache
  • all images full responsive
  • load just one image

I generate all tags but wrap them in HTML comment. Browser don't load images.

<div class="stage-image has-content">
    <div class="stage-image-item">
        <!-- <img src="/storage/hochschule/upload/150902-4654.jpg" srcset="/storage/_processed_/3/e/csm_150902-4654_8eaf6a0880.jpg 764w,/storage/_processed_/3/e/csm_150902-4654_d0f788b245.jpg 1084w,/storage/hochschule/upload/150902-4654.jpg 1170w,"  sizes="(max-width: 767px) 764px, (min-width: 768px) and (max-width: 991px) 1084px, (min-width: 992px) 1170px"  alt="" > -->
    </div>
    <div class="stage-image-item">
        <!-- <img src="/storage/hochschule/upload/151022-0041.jpg" srcset="/storage/_processed_/f/e/csm_151022-0041_d0b564fdb1.jpg 764w,/storage/_processed_/f/e/csm_151022-0041_b993bf6066.jpg 1084w,/storage/hochschule/upload/151022-0041.jpg 1170w,"  sizes="(max-width: 767px) 764px, (min-width: 768px) and (max-width: 991px) 1084px, (min-width: 992px) 1170px"  alt="" > -->
    </div>
    <div class="stage-image-item">
        <!-- <img src="/storage/hochschule/upload/151013-7145.jpg" srcset="/storage/_processed_/e/6/csm_151013-7145_42e2692215.jpg 764w,/storage/_processed_/e/6/csm_151013-7145_3ee771dee1.jpg 1084w,/storage/hochschule/upload/151013-7145.jpg 1170w,"  sizes="(max-width: 767px) 764px, (min-width: 768px) and (max-width: 991px) 1084px, (min-width: 992px) 1170px"  alt="" > -->
    </div>
    <div class="stage-image-item">
        <!-- <img src="/storage/hochschule/upload/151104-1433.jpg" srcset="/storage/_processed_/f/b/csm_151104-1433_7bf357e27a.jpg 764w,/storage/_processed_/f/b/csm_151104-1433_b7c9f24147.jpg 1084w,/storage/hochschule/upload/151104-1433.jpg 1170w,"  sizes="(max-width: 767px) 764px, (min-width: 768px) and (max-width: 991px) 1084px, (min-width: 992px) 1170px"  alt="" > -->
    </div>
    <div class="stage-image-item">
        <!-- <img src="/storage/hochschule/upload/150930-6077.jpg" srcset="/storage/_processed_/a/e/csm_150930-6077_d7a31d91e3.jpg 764w,/storage/_processed_/a/e/csm_150930-6077_87603957bf.jpg 1084w,/storage/hochschule/upload/150930-6077.jpg 1170w,"  sizes="(max-width: 767px) 764px, (min-width: 768px) and (max-width: 991px) 1084px, (min-width: 992px) 1170px"  alt="" > -->
    </div>
</div>

 

Then remove randomly HTML comment from one and browser loads them.

(function ($) {
    $.fn.rand = function () {
        return this.eq(Math.floor(Math.random() * this.length));
    };
})(jQuery);


// Get random image
var stageImageDiv = $(this).find('.stage-image');
$(stageImageDiv).children().rand().replaceWith(function () {
    var string = $(this).html();
    return string.replace(/<!--/g, '').replace(/-->/g, '');
});
Heinz Schilling
  • 2,177
  • 4
  • 18
  • 35