Uploaded images being resized

Hi All

Got an odd one where it looks like images we are uploading are getting resized by builder.io to be a max of 1500px in height. I’m trying to upload quite large portrait images but instead of being 1400px by 8000px they are getting reduced to similar to 256px by 1500px. I’ve checked in the media area by downloading the image again and it’s a reduced image size.

Anyone got any ideas?

Cheers

Tom

Hi!

Thank you for reaching out to Builder Support. My name is Veronika and I am a Customer Engineer here at Builder.

There are a few different reasons why your configuration may not be affecting your images’ dimension.

Here are two very helpful docs that have to do with Images. This one is about working with a basic image and a second one about Image-API. If you are a developer - please use the Image-API doc to customize the parameters for your image.

Can I ask how you are uploading your image? Is it through the Basic Image Block that looks like this one below?
Screenshot 2024-10-28 at 10.33.35 AM

I am thinking there could be a few things affecting your image here.

The first one is that Builder’s image block will serve the correct optimized format for each and every web browser - hence, the image block ensures that you load the smallest image possible.
Builder will analyze your image as it relates to the layout of your page and will determine the exact sizing of your image for all device sizes, automatically generating the optimal size for each screen.

The second thing that may be happening here is that there may be a parent block that’s already sized to that, in which the image is in. If this is the case, the image will automatically take up full width of the parent container when they’re dropped on the page.

Lastly, there may be some kind of design token set that would take your image and resize it to 256px by 1500px.

If you could share your API key with me - I could go in and see what’s going on a little bit better with the image resizing.

Let me know if you have any other question,

Thank you!

Thanks Veronika

Public API key: 5d0546160e954b87b82e2663451afcb1

The block is a custom component that i’ve built using Svelte. we’re uploading the image using the builder.io assets library. The field in the component is set to type:file:

{
            // This should only be for layout type with media ONLY
            // This should only be for two column layouts
            friendlyName: 'Secondary Image',
            name: 'secondaryImage',
            type: 'object',
            helperText: 'Any Aspect Ratio. Uploading a video will replace this image.',
            showIf: `options.get('layoutType') === 'Media Only' && options.get('columns') === 'Double'`,
            subFields: [
                {
                    friendlyName: 'Source',
                    name: 'src',
                    type: 'file',
                    allowedFileTypes: ['jpg', 'webm', 'gif', 'png']
                },
                {
                    friendlyName: 'Alternate Text',
                    name: 'alt',
                    type: 'text'
                }
            ]
        }
}

The actual bit of content that this is effecting is: Builder.io: Visual Development Platform

And it’s the 5th “Media and Text” component on the page - Builder ID: builder-ace0f5f5ced841e8bb1d4469c43d9103

Thanks

Tom

Thank you for the information!

Could you please send me your entire App.svelte file if possible?

That way I can try to reproduce this on my end.

Thanks!

If also possible, could you please share the component code as well?

Thanks!

Thanks Veronika

Unfortunately it’s quite a large project, but the component files are added here:

MediaAndText.svelte:

<script lang="ts">
    import SectionHeader from '../../atoms/SectionHeader/SectionHeader.svelte';
    import SectionText from '../../atoms/SectionText/SectionText.svelte';
    import {
        EMediaAndTextCopyAlignment,
        EMediaAndTextMediaPositionHorizontal,
        EMediaAndTextMediaPositionVertical,
        EMediaAndTextVerticalAlignment,
        ETitleFontSizes,
        EMediaAndTextColumnCount,
        EMediaAndTextLayoutType
    } from '../../../config/enums.js';
    import { Image } from '@builder.io/sdk-svelte';
    import VideoPlayer from '../../organisms/VideoPlayer/VideoPlayer.svelte';

    export let layoutType = EMediaAndTextLayoutType.MT;
    export let columns = EMediaAndTextColumnCount.SINGLE;
    export let fullBleed: boolean = false;

    export let heading = '';
    export let titleSize: string = ETitleFontSizes.H3;
    export let description1 = '';
    export let description2 = '';
    export let stickyText: boolean = false;
    export let copyAlignment = EMediaAndTextCopyAlignment.LEFT;
    export let verticalAlignment = EMediaAndTextVerticalAlignment.TOP;

    export let mainImage = {};
    export let mainVideoSrc = '';
    export let mediaPositionHorizontal = EMediaAndTextMediaPositionHorizontal.RIGHT;
    export let mediaPositionVertical = EMediaAndTextMediaPositionVertical.BOTTOM;
    export let secondaryImage = {};
    export let secondaryVideoSrc = '';

    export let backgroundColour = '';
    export let txtColour = '';

    function sanitizeClassName(name: string): string {
        // Remove invalid characters and replace them with a hyphen
        return name.replace(/[^a-zA-Z0-9-_]/g, '-');
    }
    const safeLayoutType: string = sanitizeClassName(layoutType);
</script>

<div class="media-and-text-wrapper" class:media-and-text-wrapper--layout-is-fullbleed={fullBleed}>
    <div
        class="
        media-and-text
        media-and-text--columns-{columns}
        media-and-text--layoutType-{safeLayoutType}
        media-and-text--mediaPositionHorizontal-is-{mediaPositionHorizontal}
        media-and-text--mediaPositionVertical-is-{mediaPositionVertical}
        media-and-text--copyAlignment-is-{copyAlignment}
        media-and-text--verticalAlignment-is-{verticalAlignment}
        "
        class:media-and-text--layout-is-fullbleed={fullBleed}
        class:media-and-text--hasSecondaryMedia={secondaryImage?.src || secondaryVideoSrc}
        class:media-and-text--hasSecondaryDescription={description2}
        style="--bg-colour: {backgroundColour}; --txt-colour: {txtColour};"
    >
        {#if layoutType !== EMediaAndTextLayoutType.MO && (heading || description1 || (columns === EMediaAndTextColumnCount.DOUBLE && layoutType === EMediaAndTextLayoutType.TO && description2))}
            <div class="media-and-text__text" class:media-and-text__text--is-sticky={stickyText}>
                {#if heading}
                    <div class="media-and-text__heading">
                        <SectionHeader {titleSize}>{heading}</SectionHeader>
                    </div>
                {/if}

                {#if description1 || (columns === EMediaAndTextColumnCount.DOUBLE && layoutType === EMediaAndTextLayoutType.TO && description2)}
                    <div class="media-and-text__descriptions">
                        {#if description1}
                            <SectionText>{@html description1}</SectionText>
                        {/if}
                        {#if columns === EMediaAndTextColumnCount.DOUBLE && layoutType === EMediaAndTextLayoutType.TO}
                            {#if description2}
                                <SectionText>{@html description2}</SectionText>
                            {/if}
                        {/if}
                    </div>
                {/if}
            </div>
        {/if}

        {#if layoutType !== EMediaAndTextLayoutType.TO}
            <div class="media-and-text__media">
                {#if mainImage?.src || mainVideoSrc}
                    <div class="media-and-text__source media-and-text__source--primary">
                        {#if mainVideoSrc}
                            <VideoPlayer src={mainVideoSrc} autoPlay={true} />
                        {:else}
                            <Image {...mainImage} />
                        {/if}
                    </div>
                {/if}
                {#if columns === EMediaAndTextColumnCount.DOUBLE && layoutType === EMediaAndTextLayoutType.MO}
                    {#if secondaryImage?.src || secondaryVideoSrc}
                        <div class="media-and-text__source media-and-text__source--secondary">
                            {#if secondaryVideoSrc}
                                <VideoPlayer src={secondaryVideoSrc} autoPlay={true} />
                            {:else}
                                <Image {...secondaryImage} />
                            {/if}
                        </div>
                    {/if}
                {/if}
            </div>
        {/if}
    </div>
</div>

<style lang="scss">
    @use '/src/scss/base';
    @use 'sass-mq/mq' as *;

    .media-and-text-wrapper {
        padding: var(--layout-pagegrid-margin);

        @include mq($from: tablet) {
            display: grid;
            column-gap: var(--layout-pagegrid-gutter);
            grid-template-columns: [first] repeat(var(--layout-pagegrid-col-count), 1fr) [end];
        }

        &--layout-is-fullbleed {
            display: block;
            padding-left: 0;
            padding-right: 0;
        }
    }

    .media-and-text {
        grid-column: 2 / -2;

        background-color: var(--bg-colour);
        color: var(--txt-colour);
        display: flex;
        flex-direction: column;
        gap: var(--layout-pagegrid-gutter);

        &--columns-Single {
            &.media-and-text--mediaPositionVertical-is-Top {
                @include mq($from: tablet) {
                    flex-direction: column-reverse;
                }
            }
            &.media-and-text--mediaPositionVertical-is-Bottom {
                @include mq($from: tablet) {
                    flex-direction: column;
                }
            }
        }

        &--columns-Double {
            &.media-and-text--mediaPositionHorizontal-is-Right {
                @include mq($from: tablet) {
                    flex-direction: row;
                }
            }
            &.media-and-text--mediaPositionHorizontal-is-Left {
                @include mq($from: tablet) {
                    flex-direction: row-reverse;
                }
            }

            &.media-and-text--verticalAlignment-is-Top {
                @include mq($from: tablet) {
                    align-items: start;
                }
            }
            &.media-and-text--verticalAlignment-is-Middle {
                @include mq($from: tablet) {
                    align-items: center;
                }
            }
            &.media-and-text--verticalAlignment-is-Bottom {
                @include mq($from: tablet) {
                    align-items: flex-end;
                }
            }
        }

        &--copyAlignment-is-Left {
        }
        &--copyAlignment-is-Right {
            @include mq($from: tablet) {
                text-align: right;
            }
        }
        &--copyAlignment-is-Center {
            @include mq($from: tablet) {
                text-align: center;
            }
        }

        &__text {
            &--is-sticky {
                @include mq($from: desktop) {
                    position: sticky;
                    top: 10rem;
                }
            }

            .media-and-text--columns-Double.media-and-text--layoutType-Media-and-Text.media-and-text--hasSecondaryDescription
                & {
                @include mq($from: tablet) {
                    flex: 0 0 50%;
                }
            }
        }

        &__heading {
        }

        &__descriptions {
            .media-and-text--columns-Single & {
                @include mq($from: tablet) {
                    width: 80%;
                }
            }

            .media-and-text--columns-Double.media-and-text--layoutType-Text-Only.media-and-text--hasSecondaryDescription
                & {
                @include mq($from: tablet) {
                    display: flex;
                    flex-direction: row;
                    gap: 2rem;
                }
            }
        }

        &__media {
            display: grid;
            gap: var(--layout-pagegrid-gutter);
            width: 100%;

            :global(picture),
            :global(img) {
                width: 100%;
            }

            .media-and-text--columns-Double.media-and-text--layoutType-Media-Only.media-and-text--hasSecondaryMedia
                & {
                @include mq($from: tablet) {
                    grid-template-columns: 1fr 1fr;
                }
            }
        }
    }

    .media-and-text :global(.section-header) {
        line-height: 1;
        margin-bottom: 1.6rem;
        margin-top: -0.7rem;

        @include mq($from: tablet) {
            margin-bottom: 3rem;
        }
    }

    .media-and-text :global(.section-text) {
        font-size: 1.4rem;
        line-height: 20px;

        @include mq($from: tablet) {
            font-size: 1.6rem;
        }
    }
    .media-and-text--columns-Double.media-and-text--layoutType-Text-Only.media-and-text--hasSecondaryDescription
        :global(.section-text) {
        @include mq($from: tablet) {
            flex: 0 0 calc(50% - var(--layout-pagegrid-gutter) / 2);
        }
    }
</style>

MediaAndText.builder.js:

import MediaAndText from './MediaAndText.svelte';
import {
    EMediaAndTextCopyAlignment,
    EMediaAndTextMediaPositionHorizontal,
    EMediaAndTextMediaPositionVertical,
    EMediaAndTextVerticalAlignment,
    ETitleFontSizes,
    EMediaAndTextColumnCount,
    EMediaAndTextLayoutType
} from '../../../config/enums.js';
import { DOCS_URL } from '../../../config/constants.js';

export default {
    component: MediaAndText,
    name: 'Media and Text',
    description: 'Allows images/videos, heading and body copy.',
    docsLink: DOCS_URL + '/storybook/?path=/docs/molecules-MediaAndText--default',
    image: 'https://unpkg.com/@tabler/icons@3.5.0/icons/outline/layout-2.svg',
    screenshot:
        'https://cdn.builder.io/api/v1/image/assets%2F5d0546160e954b87b82e2663451afcb1%2Fbad684738e154368939c8a6273d3cf6a',
    inputs: [
        {
            friendlyName: 'Layout Type',
            name: 'layoutType',
            type: 'text',
            enum: Object.values(EMediaAndTextLayoutType),
            defaultValue: EMediaAndTextLayoutType.MT
        },
        {
            friendlyName: 'Layout Columns',
            name: 'columns',
            type: 'text',
            enum: Object.values(EMediaAndTextColumnCount),
            defaultValue: EMediaAndTextColumnCount.SINGLE
        },
        {
            // This should only be for layoutType Media Only
            friendlyName: 'Full Bleed/Edge-to-Edge?',
            name: 'fullBleed',
            type: 'boolean',
            defaultValue: false,
            helperText: 'Removes side padding/margins.',
            showIf: `options.get('layoutType') === 'Media Only'`
        },
        {
            // This should only be for layoutType that has text
            friendlyName: 'Heading',
            name: 'heading',
            type: 'text',
            showIf: `options.get('layoutType') !== 'Media Only'`
        },
        {
            // This should only be for layoutType that has text
            friendlyName: 'Heading Font Size',
            name: 'titleSize',
            type: 'text',
            enum: Object.values(ETitleFontSizes),
            defaultValue: ETitleFontSizes.H3,
            showIf: `options.get('layoutType') !== 'Media Only' && options.get('heading') !== ''`
        },
        {
            // This should only be for layoutType that has text
            friendlyName: 'Description 1',
            name: 'description1',
            type: 'richText',
            helperText: 'Please use formating sparingly.',
            showIf: `options.get('layoutType') !== 'Media Only'`
        },
        {
            // This should only be for layoutType Text Only
            // This should only be for two column layouts
            friendlyName: 'Description 2',
            name: 'description2',
            type: 'richText',
            helperText: 'Please use formating sparingly.',
            showIf: `options.get('layoutType') === 'Text Only' && options.get('columns') === 'Double'`
        },
        {
            // This should only be for layoutType Media and Text
            // This should only be for two column layouts
            friendlyName: 'Sticky Text on Desktop?',
            name: 'stickyText',
            type: 'boolean',
            defaultValue: false,
            showIf: `options.get('layoutType') === 'Media and Text' && options.get('columns') === 'Double'`
        },
        {
            // This should only be for layoutType that has text
            friendlyName: 'Horizontal Text Alignment',
            name: 'copyAlignment',
            type: 'text',
            enum: Object.values(EMediaAndTextCopyAlignment),
            defaultValue: EMediaAndTextCopyAlignment.LEFT,
            showIf: `options.get('layoutType') !== 'Media Only'`
        },
        {
            // This should only be for layoutType Media and Text
            // This should only be for two column layouts
            friendlyName: 'Vertical Text Alignment',
            name: 'verticalAlignment',
            type: 'text',
            enum: Object.values(EMediaAndTextVerticalAlignment),
            defaultValue: EMediaAndTextVerticalAlignment.MIDDLE,
            showIf: `options.get('layoutType') === 'Media and Text' && options.get('columns') === 'Double'`
        },
        {
            // This should only be for layout type with media
            friendlyName: 'Main Image',
            name: 'mainImage',
            type: 'object',
            helperText: 'Any Aspect Ratio. Uploading a video will replace this image.',
            showIf: `options.get('layoutType') !== 'Text Only'`,
            subFields: [
                {
                    friendlyName: 'Source',
                    name: 'src',
                    type: 'file',
                    allowedFileTypes: ['jpg', 'webm', 'gif', 'png']
                },
                {
                    friendlyName: 'Alternate Text',
                    name: 'alt',
                    type: 'text'
                }
            ]
        },
        {
            // This should only be for layout type with media
            friendlyName: 'Main Video',
            helperText: 'Any aspect ratio. This overrides the main image.',
            name: 'mainVideoSrc',
            type: 'file',
            allowedFileTypes: ['mov', 'webm', 'mp4'],
            showIf: `options.get('layoutType') !== 'Text Only'`
        },
        {
            // This should only be for layoutType Media and Text
            // This should only be for two column layouts
            friendlyName: 'Main Media Horizontal Alignment',
            name: 'mediaPositionHorizontal',
            type: 'text',
            helperText: 'Applies to tablet and larger viewports only',
            enum: Object.values(EMediaAndTextMediaPositionHorizontal),
            defaultValue: EMediaAndTextMediaPositionHorizontal.RIGHT,
            showIf: `options.get('layoutType') === 'Media and Text' && options.get('columns') === 'Double'`
        },
        {
            // This should only be for layoutType Media and Text
            // This should only be for one column layouts
            friendlyName: 'Main Media Vertical Alignment',
            name: 'mediaPositionVertical',
            type: 'text',
            helperText: 'Applies to tablet and larger viewports only',
            enum: Object.values(EMediaAndTextMediaPositionVertical),
            defaultValue: EMediaAndTextMediaPositionVertical.BOTTOM,
            showIf: `options.get('layoutType') === 'Media and Text' && options.get('columns') === 'Single'`
        },
        {
            // This should only be for layout type with media ONLY
            // This should only be for two column layouts
            friendlyName: 'Secondary Image',
            name: 'secondaryImage',
            type: 'object',
            helperText: 'Any Aspect Ratio. Uploading a video will replace this image.',
            showIf: `options.get('layoutType') === 'Media Only' && options.get('columns') === 'Double'`,
            subFields: [
                {
                    friendlyName: 'Source',
                    name: 'src',
                    type: 'file',
                    allowedFileTypes: ['jpg', 'webm', 'gif', 'png']
                },
                {
                    friendlyName: 'Alternate Text',
                    name: 'alt',
                    type: 'text'
                }
            ]
        },
        {
            // This should only be for layout type with media ONLY
            // This should only be for two column layouts
            friendlyName: 'Secondary Video',
            helperText: 'Any aspect ratio. This overrides the secondary image.',
            name: 'secondaryVideoSrc',
            type: 'file',
            allowedFileTypes: ['mov', 'webm', 'mp4'],
            showIf: `options.get('layoutType') === 'Media Only' && options.get('columns') === 'Double'`
        },
        {
            friendlyName: 'Background Colour',
            name: 'backgroundColour',
            type: 'color'
        },
        {
            // This should only be for layout type with text
            friendlyName: 'Text Colour',
            name: 'txtColour',
            type: 'color',
            showIf: `options.get('layoutType') !== 'Media Only'`
        }
    ]
};

Hi Tom,

I have been looking into this issue as well. I can see the image is 5760 x 2680 now. Were you able to adjust the size accordingly?

Thank you.

Sorry Veronika

It’s now actually the 4th Media and Text component on the page.

So the Source field shows the image is 1621 x 8723, but if I click on the image, it opens in a new tab and opens up as 279 x 1500. And it’s this lower resolution image that is sent through to the front end as well: Explora Journeys - Skywire London

Thanks

Tom

Hi Tom,

Thank you for bearing with me as I looked into this.

Based on our documentation, it does look like we have lazy-loading and image optimization as a built-in feature to the Visual Editor - which is why this behavior is happening.

If you are looking to control the optimization for this (or any) specific image without affecting all the other images on the application, you can selectively pass URL parameters or custom logic in your component code to handle only that particular image. Here are some solutions:

When you set up the SRC for this specific image, you can manually add parameters to the URL, ensuring that the image won’t be optimized by the Builder optimization. You can either append:

?quality=100&format=auto to the image, and you can even go as far as appending the height and width parameters like so:

https://cdn.builder.io/api/v1/image/assets%2F5d0546160e954b87b82e2663451afcb1%2Feb63d26e1e084b2685b967f2b3910ef4?quality=100&width=1621&height=8723

This worked for me when I tested it locally - but please give it a try and let me know if it works for you.

Here is the Image API doc for all the different things you can do to your image.

Another option is if you’d like a more dynamic and Builder Visual Editor friendly approach, you can add a custom boolean field (something like disableOptimization) to selectively disable optimization for certain images. Then you can use this field in your component:

In your MediaAndText.builder.ts file, you can add something like:

{
  friendlyName: 'Disable Image Optimization',
  name: 'disableOptimization',
  type: 'boolean',
  helperText: 'Disable optimization for this image only'
}

and then update your MediaAndText.svelte to conditionally apply the optimization parameters.

<script lang="ts">
    import SectionHeader from '../../atoms/SectionHeader/SectionHeader.svelte';
    import SectionText from '../../atoms/SectionText/SectionText.svelte';
    import VideoPlayer from '../../organisms/VideoPlayer/VideoPlayer.svelte';
    import { Image } from '@builder.io/sdk-svelte';

    import {
        EMediaAndTextCopyAlignment,
        EMediaAndTextMediaPositionHorizontal,
        EMediaAndTextMediaPositionVertical,
        EMediaAndTextVerticalAlignment,
        ETitleFontSizes,
        EMediaAndTextColumnCount,
        EMediaAndTextLayoutType
    } from '../../../config/enums.js';

    export let layoutType = EMediaAndTextLayoutType.MT;
    export let columns = EMediaAndTextColumnCount.SINGLE;
    export let fullBleed: boolean = false;

    export let heading = '';
    export let titleSize: string = ETitleFontSizes.H3;
    export let description1 = '';
    export let description2 = '';
    export let stickyText: boolean = false;
    export let copyAlignment = EMediaAndTextCopyAlignment.LEFT;
    export let verticalAlignment = EMediaAndTextVerticalAlignment.TOP;

    export let mainImage = {};
    export let mainVideoSrc = '';
    export let mediaPositionHorizontal = EMediaAndTextMediaPositionHorizontal.RIGHT;
    export let mediaPositionVertical = EMediaAndTextMediaPositionVertical.BOTTOM;
    export let secondaryImage = {};
    export let secondaryVideoSrc = '';

    export let backgroundColour = '';
    export let txtColour = '';

    // New prop to disable optimization for the specific image
    export let disableOptimization: boolean = false;

    function sanitizeClassName(name: string): string {
        return name.replace(/[^a-zA-Z0-9-_]/g, '-');
    }
    const safeLayoutType: string = sanitizeClassName(layoutType);

    // Function to conditionally add parameters based on disableOptimization
    function getImageUrl(src: string): string {
        if (disableOptimization) {
            return `${src}?quality=100&format=auto`;
        }
        return src;
    }
</script>

Please let me know if either of these options work for you - if not, I’m happy to continue digging into this and finding a solution. Let me know if you have any further questions,

Thank you very much!

Thanks Veronika

That was very useful and have now appended ?height=10000 to the src to get the original size of the image

Thanks

Tom