Bonjour,

Peut-être la question la plus bête du mois.

Par quelle miracle serait possible que le zoom avant sur une image fasse grandir l'image sans être limité par la taille de son parent ?

Une image dans ce codepen, elle provient d'un codepen de gcyrillus, alertez-moi si souci.
Modérateur
Salut,
Je ne comprends pas bien ta question.
Sur une base simple, tu as besoin de 2 choses :
- l'event wheel
- la propriété transform scale

index.html :

<!doctype html>
<html lang="fr">

    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Titre du document</title>
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/foundation-sites@6.9.0/dist/css/foundation-float.min.css" crossorigin="anonymous">
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/foundation-sites@6.9.0/dist/css/foundation-prototype.min.css" crossorigin="anonymous">
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/foundation-sites@6.9.0/dist/css/foundation-rtl.min.css" crossorigin="anonymous">
        <link rel="stylesheet" href="styles.css">
    </head>

    <body>
        <main class="grid-container margin-top-3">
            <div class="wrap">
                <img src="https://dummyimage.com/400x300/abc" alt="">
            </div>
        </main>
        <script src="app.js"></script>
    </body>

</html>


styles.css :

.wrap{
    overflow: hidden;
    border: 1px solid red;
    display: inline-block;

    img{
        transition: transform 100ms;
    }
}

* si besoin, tu supprimes la règle : overflow hidden.

app.js :

(()=>{
    const wrap = document.querySelector('.wrap')
    const img = wrap.querySelector('img')
    let currentScale = 1
    wrap.addEventListener('wheel', e => {
        e.preventDefault()
        const MIN_SCALE = 0.1
        const MAX_SCALE = 5

        let zoomStep = e.deltaY / 500
        currentScale -= zoomStep
        
        if (currentScale < MIN_SCALE) {
            currentScale = MIN_SCALE
        }else if(currentScale > MAX_SCALE){
            currentScale = MAX_SCALE
        }
        
        img.style.transform = `scale(${currentScale.toFixed(3)})`
    })
})()

Modifié par Niuxe (03 Oct 2025 - 01:47)
Meilleure solution
Bonjour Niuxe,

Niuxe a écrit :
Je ne comprends pas bien ta question.

Que la molette fasse agrandir l'image même quand celle-ci est plein écran.
Je n'aime pas les loupes.
L'idée est de pouvoir agrandir l'image avec la molette sans être limité.

Je teste ton code dans mon codepen en fin de ,journée.

Je vous tiens informés.
Administrateur
Avec un `overflow: hidden` sur body par exemple (+ le scale proposé par Niuxe) ?

Je ne suis pas bien sûr de comprendre le besoin non plus.

EDIT : ah ben c'est déjà ce que propose Niuxe, désolé pour le dérangement Smiley cligne
Modifié par Raphael (03 Oct 2025 - 17:55)
Bonjour,

Copier-coller du code de Niuxe dans le codepen.

C'est bien l'effet voulu, personnellement je trouve cela beaucoup mieux qu'une loupe, et sans doute plus léger.

J'essaye d'intégrer ce code pour l'image principale d'un carrousel, suite de cette discussion, je reviens vers vous.
Bonjour,
avec l'effet de pixelisation qui apparaît à partir d'un certain point. Ce n'est pas très beau. Quelle utilité ?
Bonjour Bontota,

Cela dépend de la qualité de la photo.
Je vais faire des tests avec mes photos.
L'intérêt est un taux de grossissement librement décidé par l'internaute avec sa molette, il est assez malin pour revenir en arrière quand cela devient trop pixélisé.
\Ô/
boteha_2 a écrit :
C'est bien l'effet voulu, personnellement je trouve cela beaucoup mieux qu'une loupe, et sans doute plus léger.

pas sûr qu'ergonomiquement parlant cela soit une bonne chose, la molette est plutôt traditionnellement utilisé sur des cartes pour le zoom/dé-zoom, concernant les slides la molette sert plutôt a avancer/reculer dans le slide.

Il te faudra peut-être également prendre en compte les supports tactiles.
Dave-Hiock a écrit :
pas sûr qu'ergonomiquement parlant cela soit une bonne chose, la molette est plutôt traditionnellement utilisé sur des cartes pour le zoom/dé-zoom, concernant les slides la molette sert plutôt a avancer/reculer dans le slide.


C'est expérimental et l'expérience n'est pas terminée.
Ce sera expérimenté pour l'image principale d'un carrousel dans un pop-up qui ne contient que le carrousel.

Dave-Hiock a écrit :
Il te faudra peut-être également prendre en compte les supports tactiles.


Ce n'est pas prévu pour petit écran.
Cela dit je suis curieux de voir comment un mobile interprète l'événement wheel.
Modérateur
Dave-Hiock a écrit :
Il te faudra peut-être également prendre en compte les supports tactiles.


Sur un appareil tactile, on peut très bien agrandir une image à l'écran. C'est une fonctionnalité basique.

Le réel problème de cette approche à la molette, c'est que l'utilisateur n'a pas l'habitude d'utiliser la molette pour zoomer ou dézoomer.
Bonjour Niuxe,

Merci de ton suivi.

Niuxe a écrit :
Le réel problème de cette approche à la molette, c'est que l'utilisateur n'a pas l'habitude d'utiliser la molette pour zoomer ou dézoomer.


Je ne dis pas que tu n'as pas raison mais spontanément c'est ce que je fais.
Je tombe sur un carrousel.
En général cliquer sur l'image principale l'agrandit dans une modale.
Si je veux voir plus de détails je zoome avec la molette, cela semble naturel.
Et là catastrophe... sauf avec ton code.
Bonjour,

Avec le code de Niuxe le dimensionnement à la molette fonctionne bien.

Par contre, afin de pouvoir explorer les détails qui sont sortis du parent, il faut des barres de défilement.

.wrap {overflow: scroll}


Sur le codepen le comportement m'étonne.

Je peux explorer vers le bas et vers la droite mais pas vers le haut ni vers la gauche.

Autrement dit, si je grossis, la barre de défilement verticale est calée en haut et celle horizontale à gauche.

Pourquoi pas au centre de pouvoir aller dans les deux sens ?

J'espère être clair...
Bonjour,

J'ai essayé de mette .wrap en display: inline-grid avec place-content: center mais cela ne change pas le résultat.

Les barres de défilement restent calées à guauche et en haut ce qui me semble être un comportement étrange.

Pourquoi pas au centre de façon à pouvoir visualiser toute la surface de l'image qui déborde ?
Modérateur
Salut,

Il aurait fallu que tu crées un nouveau sujet. Je ne regarde pas toujours les dernières réponses dans le forum.

Pour répondre à ton souci, autant faire un webcomponent comme ceci. Le codepen est ici : https://codepen.io/niuxe/pen/wBGaYEG

le html (index.html):

<!doctype html>
<html lang="fr">

    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Titre du document</title>
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/foundation-sites@6.9.0/dist/css/foundation-float.min.css" crossorigin="anonymous">
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/foundation-sites@6.9.0/dist/css/foundation-prototype.min.css" crossorigin="anonymous">
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/foundation-sites@6.9.0/dist/css/foundation-rtl.min.css" crossorigin="anonymous">
        <link rel="stylesheet" href="styles.css">
    </head>

    <body>
        <main class="grid-container margin-top-3">
            <zoomable-image src="https://picsum.photos/id/145/400/300" />
        </main>
        <script src="app.js"></script>
    </body>

</html>


le css (styles.css) :

.zoomable-image {
    display: inline-block;
    border: 1px solid red;
    overflow: hidden;
    cursor: grab;
    width: 400px;
    height: 300px;
    position: relative;

    img {
        display: block;
        margin: auto;
        user-select: none;
        pointer-events: none;
        transform-origin: center center;
        transition: transform 0.1s ease-out;
    }
}


le js (app.js):

class ZoomableImage extends HTMLElement {
    constructor() {
        super()
        this.img = document.createElement('img')
        this.appendChild(this.img)

        this.currentScale = 1
        this.pos = { x: 0, y: 0  }
        this.start = { x: 0, y: 0  }
        this.isDragging = false
    }

    connectedCallback() {
        if (this.hasAttribute('src')) {
            this.img.src = this.getAttribute('src')

        }
        this.classList.add('zoomable-image')
        this.bindEvents()
    }

    bindEvents() {
        this.addEventListener('wheel', e => this.onWheel(e))
        this.addEventListener('mousedown', e => this.onMouseDown(e))
        this.addEventListener('mousemove', e => this.onMouseMove(e))
        this.addEventListener('mouseup', () => this.onMouseUp())
        this.addEventListener('mouseleave', () => this.onMouseLeave())
    }

    onWheel(e) {
        e.preventDefault()
        const MIN_SCALE = 1
        const MAX_SCALE = 5
        const zoomSpeed = 0.0015
        const oldScale = this.currentScale

        this.currentScale -= e.deltaY * zoomSpeed
        this.currentScale = Math.min(Math.max(this.currentScale, MIN_SCALE), MAX_SCALE)

        const rect = this.getBoundingClientRect()
        const dx = e.clientX - rect.left - rect.width / 2
        const dy = e.clientY - rect.top - rect.height / 2
        this.pos.x -= dx * (this.currentScale / oldScale - 1)
        this.pos.y -= dy * (this.currentScale / oldScale - 1)

        this.updateTransform()
    }

    onMouseDown(e) {
        if (this.currentScale <= 1) return
        this.isDragging = true
        this.start.x = e.clientX - this.pos.x
        this.start.y = e.clientY - this.pos.y
        this.style.cursor = 'grabbing'
    }

    onMouseMove(e) {
        if (!this.isDragging) return
        this.pos.x = e.clientX - this.start.x
        this.pos.y = e.clientY - this.start.y
        this.updateTransform()
    }

    onMouseUp() {
        this.isDragging = false
        this.style.cursor = 'grab'
    }

    onMouseLeave() {
        this.isDragging = false
        this.style.cursor = 'grab'
    }

    updateTransform() {
        if (this.currentScale === 1) {
            this.pos.x = 0
            this.pos.y = 0
        }
        this.img.style.transform = `translate(${this.pos.x}px, ${this.pos.y}px) scale(${this.currentScale})`
    }
}

customElements.define('zoomable-image', ZoomableImage)

Modifié par Niuxe (12 Nov 2025 - 14:51)
Bonjour Niuxe,

Merci de ton suivi.

L'effet semble parfait, c'est costaud.

Je regarde cela en détail et je reviens vers vous.
Bonjour Niuxe,

L'effet est exactement ce que je recherche, merci pour cette trouvaille.

Par contre, est-on obligé de passer par l'élément zoomable-image ?

zoomable-image

Non reconnu par le W3C (signalé comme erreur).

Et surtout je ne vois comment intégrer ce code à mon code de carrousel CSS
Il s'agit de cibler juste l'image principale, pas les vignettes.
Bonjour,

J'aimerais m'en sortir sans webcomponent.

En partant de cette structure html :

<div class="wrap">
<img src="https://dummyimage.com/400x300/abc" alt="">
</div>


Je vais donc essayer de récupérer le code JS du webcomponent pour l'adapter à ce code html.

Je suppose que c'est possible, non ?
Modifié par boteha_2 (19 Nov 2025 - 23:14)