Custom Element Interaction with Browser Scroll

Question:
How to make custom element interact with the browser scroll?

Product:
Wix Editor

What are you trying to achieve:
I have created a custom Element that changes images based on browser scroll, the images are changing but the getBoundingClient() for the custom element is not working as expected, help would be appreciated

What have you already tried:
I have tried referencing the custom element as this.getBoundingClientRect() but it’s not working as expected

Additional information:
Here’s the code: Please note that this is a custom element code and the custom element is used on a page of wix editor

 const init = () => {
        preloadImages(images, () => {
            // const height = calHeight(images, this.frequency);
            imageSequenceContainer.style.height = `100vh`;
            // heightDiv.style.height = `${height}px`;
            // window.addEventListener('wheel', throttle(onScroll, 50));
            window.addEventListener('wheel', onScroll);
            drawImage(loadedImages[0]);
        });
    };

    const onScroll = () => {
        changeImage();
    };

    const changeImage = () => {
        // const coordinates = heightDiv.getBoundingClientRect();
        const iCoordinates = this.getBoundingClientRect();
        if (iCoordinates.top > 200) {
            imageIndex=0;
            drawImage(loadedImages[0]);
            return;
        } else {
            if (imageIndex >= images.length) {
                imageIndex = images.length - 1;
            }
            if (imageIndex < images.length) {
                drawImage(loadedImages[imageIndex]);
            }
            drawImage(loadedImages[imageIndex++])
        }

        // // const heightDivHeight = parseInt(heightDiv.style.height, 10);
        // const top = this.changeFrom + containerHeight;
        // const bottom = -(heightDivHeight - containerHeight);
        // const range = bottom - top;
        // const interval = range / images.length;

        //   imageIndex = Math.abs(Math.floor((top - coordinates.y) / interval));

    };

    const drawImage = (img) => {
        const canvasWidth = canvas.width = window.innerWidth;
        const canvasHeight = canvas.height = window.innerHeight;
        const aspectRatio = img.width / img.height;
        let newWidth, newHeight;

        if (canvasWidth / aspectRatio <= canvasHeight) {
            newWidth = canvasWidth;
            newHeight = canvasWidth / aspectRatio;
        } else {
            newHeight = canvasHeight;
            newWidth = canvasHeight * aspectRatio;
        }

        const x = (canvasWidth - newWidth) / 2;
        const y = (canvasHeight - newHeight) / 2;

        ctx.clearRect(0, 0, canvas.width, canvas.height);
        ctx.drawImage(img, x, y, newWidth, newHeight);
    };

Not sure if this will work, you will have to optimize it…

Approach 1: Using getBoundingRect directly in the Custom Element

The example I provided earlier where the custom element calculates its own coordinates using this.getBoundingClientRect() is also a valid approach. Here’s a quick reminder of that:

class ImageSequenceElement extends HTMLElement {
    constructor() {
        super();
        this.images = [];
        this.loadedImages = [];
        this.imageIndex = 0;
        this.canvas = document.createElement('canvas');
        this.ctx = this.canvas.getContext('2d');
        this.appendChild(this.canvas);
    }

    connectedCallback() {
        this.preloadImages(this.images, () => {
            this.canvas.style.height = `100vh`;
            window.addEventListener('wheel', this.onScroll.bind(this));
            this.drawImage(this.loadedImages[0]);
        });
    }

    preloadImages(images, callback) {
        let loadedCount = 0;
        images.forEach((src, index) => {
            const img = new Image();
            img.src = src;
            img.onload = () => {
                this.loadedImages[index] = img;
                loadedCount++;
                if (loadedCount === images.length) {
                    callback();
                }
            };
        });
    }

    onScroll() {
        this.changeImage();
    }

    changeImage() {
        const iCoordinates = this.getBoundingClientRect();
        console.log('Element Coordinates:', iCoordinates);

        if (iCoordinates.top > 200) {
            this.imageIndex = 0;
            this.drawImage(this.loadedImages[0]);
            return;
        } else {
            if (this.imageIndex >= this.images.length) {
                this.imageIndex = this.images.length - 1;
            }
            if (this.imageIndex < this.images.length) {
                this.drawImage(this.loadedImages[this.imageIndex]);
            }
            this.drawImage(this.loadedImages[this.imageIndex++]);
        }
    }

    drawImage(img) {
        const canvasWidth = this.canvas.width = window.innerWidth;
        const canvasHeight = this.canvas.height = window.innerHeight;
        const aspectRatio = img.width / img.height;
        let newWidth, newHeight;

        if (canvasWidth / aspectRatio <= canvasHeight) {
            newWidth = canvasWidth;
            newHeight = canvasWidth / aspectRatio;
        } else {
            newHeight = canvasHeight;
            newWidth = canvasHeight * aspectRatio;
        }

        const x = (canvasWidth - newWidth) / 2;
        const y = (canvasHeight - newHeight) / 2;

        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
        this.ctx.drawImage(img, x, y, newWidth, newHeight);
    }
}

// Define the custom element
customElements.define('image-sequence-element', ImageSequenceElement);

Approach-2: Using getBoundingRect() from Wix-Page, sending it to the CE…

class ImageSequenceElement extends HTMLElement {
    constructor() {
        super();
        this.images = []; // Array to hold your images
        this.loadedImages = []; // Array to hold preloaded images
        this.imageIndex = 0; // To track the current image index
        this.canvas = document.createElement('canvas'); // Create a canvas element
        this.ctx = this.canvas.getContext('2d');
        this.appendChild(this.canvas); // Append the canvas to the custom element
    }

    connectedCallback() {
        // Preload images and initialize
        this.preloadImages(this.images, () => {
            this.canvas.style.height = `100vh`;
            window.addEventListener('wheel', this.onScroll.bind(this));
            this.drawImage(this.loadedImages[0]);
        });
    }

    preloadImages(images, callback) {
        // Logic to preload images
        let loadedCount = 0;
        images.forEach((src, index) => {
            const img = new Image();
            img.src = src;
            img.onload = () => {
                this.loadedImages[index] = img;
                loadedCount++;
                if (loadedCount === images.length) {
                    callback();
                }
            };
        });
    }

    onScroll() {
        this.changeImage();
    }

    updateCoordinates(coordinates) {
        this.iCoordinates = coordinates;
        this.changeImage();
    }

    changeImage() {
        if (!this.iCoordinates) return;

        console.log('Element Coordinates:', this.iCoordinates); // Debugging log

        if (this.iCoordinates.top > 200) {
            this.imageIndex = 0;
            this.drawImage(this.loadedImages[0]);
            return;
        } else {
            if (this.imageIndex >= this.images.length) {
                this.imageIndex = this.images.length - 1;
            }
            if (this.imageIndex < this.images.length) {
                this.drawImage(this.loadedImages[this.imageIndex]);
            }
            this.drawImage(this.loadedImages[this.imageIndex++]);
        }
    }

    drawImage(img) {
        const canvasWidth = this.canvas.width = window.innerWidth;
        const canvasHeight = this.canvas.height = window.innerHeight;
        const aspectRatio = img.width / img.height;
        let newWidth, newHeight;

        if (canvasWidth / aspectRatio <= canvasHeight) {
            newWidth = canvasWidth;
            newHeight = canvasWidth / aspectRatio;
        } else {
            newHeight = canvasHeight;
            newWidth = canvasHeight * aspectRatio;
        }

        const x = (canvasWidth - newWidth) / 2;
        const y = (canvasHeight - newHeight) / 2;

        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
        this.ctx.drawImage(img, x, y, newWidth, newHeight);
    }
}

// Define the custom element
customElements.define('image-sequence-element', ImageSequenceElement);
1 Like