javascriptcsstailwind-csscss-gridalpine.js

Tailwind: dynamic grid columns with no gaps


The documentation is not very clear about all the grid options there.

I have this grid template in alpinejs styling it with tailwind:

<div x-show="multiple" class="w-auto grid mt-3" :style="`grid-template-columns: 
 repeat(${images.length < 6 ? images.length : 6}, minmax(0, 1fr))`">
    <template x-for="image in images" :key="image.name">
        <div
            x-data="{
                imgUrl: null
            }"
            x-init="() => {
                let reader = new FileReader();

                reader.onload = e => {
                    imgUrl = e.target.result;
                };

                console.log('image');
                console.log(image);

                reader.readAsDataURL(image);
            }"
        >
        <img :src="imgUrl" alt="image.name" class="h-24 w-auto">
        </div>
    </template>
</div>

The problem I'm facing is that the grid container seems to be keeping the full width of its parent and the grid elements get these huge gaps between:

enter image description here

What I want is that each image to be nicely aligned on the left next to each other.

Snippet here

<script src="https://cdn.tailwindcss.com"></script>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>

<style>
[x-cloak] { display: none !important }
</style>

<div
    x-data="{
        inputId: 'blabla',
        selectedFileName: '',
        fileInput: null,
        multiple: true,
        images: [],
        imgCols: 0
    }"
    x-init="() => {
        fileInput = document.getElementById(inputId);
    }"
    class="w-full"
    x-on:drop="$event => {
        $event.preventDefault();
        const dataTransfer = new DataTransfer();

        if (multiple) {
            Array.from(fileInput.files).forEach(file => {
            dataTransfer.items.add(file);
            });
        }

        Array.from($event.dataTransfer.files).forEach(file => {
            dataTransfer.items.add(file);
        });

        fileInput.files = dataTransfer.files;

        if (!multiple) {
            selectedFileName = $event.dataTransfer.files[0].name;
        } else {
            images = Array.from(fileInput.files);
        }

        fileInput.dispatchEvent(new Event('change', { bubbles: true }));
    }"
    x-on:dragover.prevent="() => console.log('drag over prevented')"
>
    <label class="flex justify-center w-full h-32 px-4 transition bg-white border-2 border-gray-300 border-dashed rounded-md appearance-none cursor-pointer hover:border-gray-400 focus:outline-none"
    >
        <span class="flex items-center space-x-2">
            <x-icon name="upload" />
            <span class="font-medium text-gray-600">
                drop or
                <span class="text-blue-600 underline">browse</span>
            </span>
        </span>
        <input id="blabla" type="file" name="file_upload" >
    </label>
    <div x-cloak class="w-full text-center" x-show="selectedFileName">
        files selected: <span x-text="selectedFileName"></span>
    </div>
    <div x-show="multiple" class="w-auto grid mt-3" :style="`grid-template-columns: repeat(${images.length < 6 ? images.length : 6}, minmax(0, 1fr))`">
        <template x-for="image in images" :key="image.name">
            <div
                x-data="{
                    imgUrl: null
                }"
                x-init="() => {
                    let reader = new FileReader();

                    reader.onload = e => {
                        imgUrl = e.target.result;
                    };

                    reader.readAsDataURL(image);
                }"
            >
            <img :src="imgUrl" alt="image.name" class="h-24 w-auto">
            </div>
        </template>
    </div>
</div>

Thanks.


Solution

  • The white space is caused by the grid track columns being wider than the images they contain. This is due to the minmax(0, 1fr) value inside the repeat(). This value means that the grid column tracks are allowed to increase in width to 1 "share" of space.

    To prevent this behavior, you could consider using a different value inside the repeat(), like max-content:

    <script src="https://cdn.tailwindcss.com"></script>
    <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
    
    <style>
      [x-cloak] {
        display: none !important;
      }
    </style>
    
    <div
      x-data="{
        inputId: 'blabla',
        selectedFileName: '',
        fileInput: null,
        multiple: true,
        images: [],
        imgCols: 0
      }"
      x-init="() => {
        fileInput = document.getElementById(inputId);
      }"
      class="w-full"
      x-on:drop="$event => {
        $event.preventDefault();
        const dataTransfer = new DataTransfer();
    
        if (multiple) {
          Array.from(fileInput.files).forEach(file => {
            dataTransfer.items.add(file);
          });
        }
    
        Array.from($event.dataTransfer.files).forEach(file => {
          dataTransfer.items.add(file);
        });
    
        fileInput.files = dataTransfer.files;
    
        if (!multiple) {
            selectedFileName = $event.dataTransfer.files[0].name;
        } else {
            images = Array.from(fileInput.files);
        }
    
        fileInput.dispatchEvent(new Event('change', { bubbles: true }));
      }"
      x-on:dragover.prevent="() => console.log('drag over prevented')"
    >
      <label
        class="flex justify-center w-full h-32 px-4 transition bg-white border-2 border-gray-300 border-dashed rounded-md appearance-none cursor-pointer hover:border-gray-400 focus:outline-none"
      >
        <span class="flex items-center space-x-2">
          <x-icon name="upload" />
          <span class="font-medium text-gray-600">
            drop or
            <span class="text-blue-600 underline">browse</span>
          </span>
        </span>
        <input id="blabla" type="file" name="file_upload" />
      </label>
      <div x-cloak class="w-full text-center" x-show="selectedFileName">
        files selected: <span x-text="selectedFileName"></span>
      </div>
      <div
        x-show="multiple"
        class="w-auto grid mt-3"
        :style="`grid-template-columns: repeat(${images.length < 6 ? images.length : 6}, max-content)`"
      >
        <template x-for="image in images" :key="image.name">
          <div
            x-data="{
              imgUrl: null
            }"
            x-init="() => {
              let reader = new FileReader();
    
              reader.onload = e => {
                imgUrl = e.target.result;
              };
    
              reader.readAsDataURL(image);
            }"
          >
            <img :src="imgUrl" alt="image.name" class="h-24 w-auto" />
          </div>
        </template>
      </div>
    </div>