Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

phayes0289's avatar

How can I set the width of an Uploaded CKEDITOR image to 100%?

I have CKEditor5 installed as my HTML editor inside my Laravel 9 project. The way I have the code now, when the image is uploaded, the original is left alone and is set as default. In addition, two new images are created. One is a thumbnail. The other is a preview (800 pixels wide). When the image is brought into the editor, the image is set to width=”800”. I need to change the with to be width=”100%”. I tried using the following javascript code to accomplish it, but it is not working. Any suggestions?

This is my textarea that is converted to a CKEditor

<textarea name="body" id="body">{{ $kb->body }}</textarea>

This is the javascript code (broken into four script blocks) for the HTML editor. The main init script, a link to the CKEDITOR5 javscript, a custom uploader and a size modifier.:

    <script src="{{ asset('/plugins/ckeditor5/ckeditor.js') }}"></script>
    <script>
        class MyUploadAdapter {
            constructor(loader) {
                // The file loader instance to use during the upload.
                this.loader = loader;
            }

            // Starts the upload process.
            upload() {
                return this.loader.file
                    .then(file => new Promise((resolve, reject) => {
                        this._initRequest();
                        this._initListeners(resolve, reject, file);
                        this._sendRequest(file);
                    }));
            }

            // Aborts the upload process.
            abort() {
                if (this.xhr) {
                    this.xhr.abort();
                }
            }

            // Initializes the XMLHttpRequest object using the URL passed to the constructor.
            _initRequest() {
                const xhr = this.xhr = new XMLHttpRequest();

                // Note that your request may look different. It is up to you and your editor
                // integration to choose the right communication channel. This example uses
                // a POST request with JSON as a data structure but your configuration
                // could be different.
                xhr.open('POST', "{{ route('image.upload', ['_token' => csrf_token()]) }}", true);
                xhr.responseType = 'json';
            }

            // Initializes XMLHttpRequest listeners.
            _initListeners(resolve, reject, file) {
                const xhr = this.xhr;
                const loader = this.loader;
                const genericErrorText = `Couldn't upload file: ${ file.name }.`;

                xhr.addEventListener('error', () => reject(genericErrorText));
                xhr.addEventListener('abort', () => reject());
                xhr.addEventListener('load', () => {
                    const response = xhr.response;

                    // This example assumes the XHR server's "response" object will come with
                    // an "error" which has its own "message" that can be passed to reject()
                    // in the upload promise.
                    //
                    // Your integration may handle upload errors in a different way so make sure
                    // it is done properly. The reject() function must be called when the upload fails.
                    if (!response || response.error) {
                        return reject(response && response.error ? response.error.message : genericErrorText);
                    }

                    // If the upload is successful, resolve the upload promise with an object containing
                    // at least the "default" URL, pointing to the image on the server.
                    // This URL will be used to display the image in the content. Learn more in the
                    // UploadAdapter#upload documentation.
                    resolve(response);
                });

                // Upload progress when it is supported. The file loader has the #uploadTotal and #uploaded
                // properties which are used e.g. to display the upload progress bar in the editor
                // user interface.
                if (xhr.upload) {
                    xhr.upload.addEventListener('progress', evt => {
                        if (evt.lengthComputable) {
                            loader.uploadTotal = evt.total;
                            loader.uploaded = evt.loaded;
                        }
                    });
                }
            }

            // Prepares the data and sends the request.
            _sendRequest(file) {
                // Prepare the form data.
                const data = new FormData();

                data.append('upload', file);

                // Important note: This is the right place to implement security mechanisms
                // like authentication and CSRF protection. For instance, you can use
                // XMLHttpRequest.setRequestHeader() to set the request headers containing
                // the CSRF token generated earlier by your application.

                // Send the request.
                this.xhr.send(data);
            }
        }

        function MyCustomUploadAdapterPlugin(editor) {
            editor.plugins.get('FileRepository').createUploadAdapter = (loader) => {
                // Configure the URL to the upload script in your back-end here!
                return new MyUploadAdapter(loader);
            };
        }
    </script>
    <script>
      class MyCustomImagePlugin {
    static get pluginName() {
        return 'MyCustomImagePlugin';
    }

    constructor(editor) {
        console.log('MyCustomImagePlugin constructor called');
        this.editor = editor;
    }

    init() {
        const editor = this.editor;

        // Modify the image output to set width to 100%
        editor.conversion.for('downcast').add(dispatcher => {
            dispatcher.on('attribute:src:image', (evt, data, conversionApi) => {
                const viewWriter = conversionApi.writer;
                const viewFigure = conversionApi.mapper.toViewElement(data.item);
                const viewImg = viewFigure.getChild(0);

                if (data.attributeNewValue !== null) {
                    viewWriter.setAttribute('src', data.attributeNewValue, viewImg);
                } else {
                    viewWriter.removeAttribute('src', viewImg);
                }
            });

            dispatcher.on('insert:image', (evt, data, conversionApi) => {
                const viewWriter = conversionApi.writer;
                const viewFigure = conversionApi.mapper.toViewElement(data.item);
                const viewImg = viewFigure.getChild(0);

                viewWriter.setStyle('width', '100%', viewImg);
                // If you need to set other styles, you can do so here
            });
        });
    }
}
    </script>
    <script>
        ClassicEditor
            .create(document.querySelector('#body'), {
                extraPlugins: [MyCustomUploadAdapterPlugin, MyCustomImagePlugin],

                // More configuration options.
                // ...
            })
            .catch(error => {
                console.log(error);
            });
    </script>

This is my ImageUploadController:

class ImageUploadController extends Controller
{
    public function upload(Request $request)
    {
        // Validate the uploaded file
        $request->validate([
            'upload' => 'required|image|mimes:jpeg,png,jpg,gif|max:10000', // Example validation rules
        ]);

        if ($request->hasFile('upload')) {
            $uploadedFile = $request->file('upload');

            // Generate a unique filename
            $filename = pathinfo($uploadedFile->getClientOriginalName(), PATHINFO_FILENAME);
            $extension = $uploadedFile->getClientOriginalExtension();
            $filenametostore = $filename . '_' . time() . '.' . $extension;

            // Store the uploaded file
            $uploadedFile->storeAs('public/uploads/posts', $filenametostore);

            // Create and store thumbnail
            $thumbnail = Image::make($uploadedFile)->resize(500, 150, function ($constraint) {
                $constraint->aspectRatio();
            });
            $thumbnailPath = 'public/uploads/posts/thumbnail/' . $filenametostore;
            Storage::put($thumbnailPath, (string)$thumbnail->encode());

            // Create and store Preview
            $preview = Image::make($uploadedFile)->resize(800, null, function ($constraint) {
                $constraint->aspectRatio();
            });
            $previewPath = 'public/uploads/posts/preview/' . $filenametostore;
            Storage::put($previewPath, (string)$preview->encode());

            Log::info('storage/uploads/post/' . $filenametostore);
            // Return response
            return response()->json([
                'default' => asset('storage/uploads/posts/' . $filenametostore),
                '800' => asset('storage/uploads/posts/preview/' . $filenametostore),
            ]);
        }

        // Handle case where no file is uploaded
        return response()->json(['error' => 'No file uploaded'], 400);
    }
}

The image uploader and the thumbnail creator code is working as expected. However, the code that displays the image is saved as follows:

<figure class="image">
    <img src="https://dev.fireops.local/storage/uploads/posts/27971931_10155469749288693_4937844076375170817_n_1710860566.jpg" 
    srcset="https://dev.fireops.local/storage/uploads/posts/preview/27971931_10155469749288693_4937844076375170817_n_1710860566.jpg 800w" 
    sizes="100vw" 
    width="800">
</figure>

How can I change with width to be 100% instead of 800px?

0 likes
1 reply
LaryAI's avatar
Level 58

To set the width of the uploaded image to 100% in CKEditor 5, you need to adjust the image conversion in the MyCustomImagePlugin plugin. The insert:image event handler should set the style attribute to width: 100% for the image element. However, it seems that you are trying to set the width directly on the img tag, which is not the recommended way to handle responsive images in CKEditor 5.

Instead, you should use CSS to control the image width. Here's how you can modify your MyCustomImagePlugin to ensure that images are styled correctly:

class MyCustomImagePlugin {
    static get pluginName() {
        return 'MyCustomImagePlugin';
    }

    constructor(editor) {
        this.editor = editor;
    }

    init() {
        const editor = this.editor;

        // Listen for the conversion from the model to the view.
        editor.conversion.for('downcast').add(dispatcher => {
            dispatcher.on('insert:image', (evt, data, conversionApi) => {
                const viewWriter = conversionApi.writer;
                const viewFigure = conversionApi.mapper.toViewElement(data.item);
                const viewImg = viewFigure.getChild(0);

                // Set the style attribute to width: 100% for the image element.
                viewWriter.setStyle('width', '100%', viewImg);
            });
        });
    }
}

Additionally, you should ensure that your CSS is set up to allow images to be responsive. You can add the following CSS rule to your stylesheet:

.ck-content img {
    width: 100%;
    height: auto;
}

This CSS rule targets images within the content area of CKEditor and ensures they take up 100% of the width of their container while maintaining their aspect ratio.

Finally, make sure to include the MyCustomImagePlugin in the list of plugins when initializing CKEditor:

ClassicEditor
    .create(document.querySelector('#body'), {
        extraPlugins: [MyCustomUploadAdapterPlugin, MyCustomImagePlugin],
        // ... other configuration options
    })
    .catch(error => {
        console.error(error);
    });

With these changes, the images inserted into the editor should now be responsive and take up the full width of their container.

Please or to participate in this conversation.