base on πŸ”ŽπŸ–Ό A JavaScript library for zooming images like Medium <p align="center"> <a href="https://medium-zoom.francoischalifour.com"><img src="logo.svg" alt="Demo" width="64"></a> <h3 align="center">medium-zoom</h3> <p align="center">A JavaScript library for zooming images like Medium</p> </p> <p align="center"> <a href="https://www.npmjs.com/package/medium-zoom"> <img src="https://img.shields.io/npm/v/medium-zoom.svg?style=flat-square" alt="version"> </a> <a href="https://github.com/francoischalifour/medium-zoom/blob/master/LICENSE"> <img src="https://img.shields.io/npm/l/medium-zoom.svg?style=flat-square" alt="MIT license"> </a> <a href="http://npmcharts.com/compare/medium-zoom"> <img src="https://img.shields.io/npm/dm/medium-zoom.svg?style=flat-square" alt="downloads"> </a> <br> <a href="https://unpkg.com/medium-zoom/dist/"> <img src="http://img.badgesize.io/https://unpkg.com/medium-zoom/dist/medium-zoom.min.js?compression=gzip&label=gzip%20size&style=flat-square" alt="gzip size"> </a> <a href="https://github.com/francoischalifour/medium-zoom/blob/master/package.json"> <img src="https://img.shields.io/badge/dependencies-none-lightgrey.svg?style=flat-square" alt="no dependencies"> </a> </p> <p align="center"> <a href="https://medium-zoom.francoischalifour.com"> <img src="https://user-images.githubusercontent.com/6137112/43369906-7623239a-9376-11e8-978b-6e089be499fb.gif" alt="Medium Zoom Demo"> </a> <br> <br> <strong> <a href="https://codesandbox.io/s/github/francoischalifour/medium-zoom/tree/master/website">πŸ”¬ Playground</a> ・ <a href="https://medium-zoom.francoischalifour.com">πŸ”Ž Demo</a> ・ <a href="https://medium-zoom.francoischalifour.com/storybook">πŸ“š Storybook</a> </strong> </p> <details> <summary><strong>Contents</strong></summary> <!-- Generate the table of contents using: ``` npx doctoc README.md --maxlevel 3 ``` --> <!-- START doctoc generated TOC please keep comment here to allow auto update --> <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> - [Features](#features) - [Installation](#installation) - [Usage](#usage) - [API](#api) - [Selectors](#selectors) - [Options](#options) - [Methods](#methods) - [Attributes](#attributes) - [Events](#events) - [Framework integrations](#framework-integrations) - [Examples](#examples) - [Debugging](#debugging) - [Browser support](#browser-support) - [Contributing](#contributing) - [License](#license) <!-- END doctoc generated TOC please keep comment here to allow auto update --> </details> ## Features - πŸ“± **Responsive** β€” scale on mobile and desktop - πŸš€ **Performant and lightweight** β€” optimized to reach 60 [fps](https://en.wikipedia.org/wiki/Framerate) - ⚑️ **High definition support** β€” load the HD version of your image on zoom - πŸ”Ž **Flexibility** β€” apply the zoom to a selection of images - πŸ–± **Mouse, keyboard and gesture friendly** β€” click anywhere, press a key or scroll away to close the zoom - πŸŽ‚ **Event handling** β€” trigger events when the zoom enters a new state - πŸ“¦ **Customization** β€” set your own margin, background and scroll offset - πŸ”§ **Pluggable** β€” add your own features to the zoom - πŸ’Ž **Custom templates** β€” extend the default look to match the UI of your app - πŸ”Œ [**Framework agnostic**](#framework-integrations) β€” works with React, Vue, Angular, Svelte, Solid, etc. ## Installation The module is available on the [npm](https://www.npmjs.com) registry. ```sh npm install medium-zoom # or yarn add medium-zoom ``` ###### Download - [Normal](https://cdn.jsdelivr.net/npm/medium-zoom/dist/medium-zoom.js) - [Minified](https://cdn.jsdelivr.net/npm/medium-zoom/dist/medium-zoom.min.js) ###### CDN - [jsDelivr](https://www.jsdelivr.com/package/npm/medium-zoom) - [unpkg](https://unpkg.com/medium-zoom/) - [esm.sh](https://esm.sh/medium-zoom) - [cdnjs](https://cdnjs.com/libraries/medium-zoom) ## Usage > [Try it out in the browser](https://codesandbox.io/s/github/francoischalifour/medium-zoom/tree/master/website) Import the library as a module: ```js import mediumZoom from 'medium-zoom' ``` Or import the library with a script tag: ```html <script src="node_modules/medium-zoom/dist/medium-zoom.min.js"></script> ``` That's it! You don't need to import any CSS styles. Assuming you add the `data-zoomable` attribute to your images: ```js mediumZoom('[data-zoomable]') ``` > [!TIP] > If you want to control when to inject the Medium Zoom CSS styles, you can use the pure JavaScript bundle: > > ```js > import mediumZoom from 'medium-zoom/dist/pure' > import 'medium-zoom/dist/style.css' > ``` ## API ```ts mediumZoom(selector?: string | HTMLElement | HTMLElement[] | NodeList, options?: object): Zoom ``` ### Selectors The selector allows attaching images to the zoom. It can be of the following types: - [CSS selectors](https://developer.mozilla.org/docs/Web/CSS/CSS_Selectors) - [`HTMLElement`](https://developer.mozilla.org/docs/Web/API/HTMLElement) - [`NodeList`](https://developer.mozilla.org/docs/Web/API/NodeList) - [`Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array) ```js // CSS selector mediumZoom('[data-zoomable]') // HTMLElement mediumZoom(document.querySelector('#cover')) // NodeList mediumZoom(document.querySelectorAll('[data-zoomable]')) // Array const images = [ document.querySelector('#cover'), ...document.querySelectorAll('[data-zoomable]'), ] mediumZoom(images) ``` ### Options The options enable the customization of the zoom. They are defined as an object with the following properties: | Property | Type | Default | Description | | -------------- | ------------------------------------- | -------- | --------------------------------------------------------------------------- | | `margin` | `number` | `0` | The space outside the zoomed image | | `background` | `string` | `"#fff"` | The background of the overlay | | `scrollOffset` | `number` | `40` | The number of pixels to scroll to close the zoom | | `container` | `string` \| `HTMLElement` \| `object` | `null` | The viewport to render the zoom in<br> [Read more β†’](docs/container.md) | | `template` | `string` \| `HTMLTemplateElement` | `null` | The template element to display on zoom<br> [Read more β†’](docs/template.md) | ```js mediumZoom('[data-zoomable]', { margin: 24, background: '#BADA55', scrollOffset: 0, container: '#zoom-container', template: '#zoom-template', }) ``` ### Methods #### `open({ target?: HTMLElement }): Promise<Zoom>` Opens the zoom and returns a promise resolving with the zoom. ```js const zoom = mediumZoom('[data-zoomable]') zoom.open() ``` _Emits an event [`open`](#events) on animation start and [`opened`](#events) when completed._ #### `close(): Promise<Zoom>` Closes the zoom and returns a promise resolving with the zoom. ```js const zoom = mediumZoom('[data-zoomable]') zoom.close() ``` _Emits an event [`close`](#events) on animation start and [`closed`](#events) when completed._ #### `toggle({ target?: HTMLElement }): Promise<Zoom>` Opens the zoom when closed / dismisses the zoom when opened, and returns a promise resolving with the zoom. ```js const zoom = mediumZoom('[data-zoomable]') zoom.toggle() ``` #### `attach(...selectors: string[] | HTMLElement[] | NodeList[] | Array[]): Zoom` Attaches the images to the zoom and returns the zoom. ```js const zoom = mediumZoom() zoom.attach('#image-1', '#image-2') zoom.attach( document.querySelector('#image-3'), document.querySelectorAll('[data-zoomable]') ) ``` #### `detach(...selectors: string[] | HTMLElement[] | NodeList[] | Array[]): Zoom` Releases the images from the zoom and returns the zoom. ```js const zoom = mediumZoom('[data-zoomable]') zoom.detach('#image-1', document.querySelector('#image-2')) // detach two images zoom.detach() // detach all images ``` _Emits an event [`detach`](#events) on the image._ #### `update(options: object): Zoom` Updates the options and returns the zoom. ```js const zoom = mediumZoom('[data-zoomable]') zoom.update({ background: '#BADA55' }) ``` _Emits an event [`update`](#events) on each image of the zoom._ #### `clone(options?: object): Zoom` Clones the zoom with provided options merged with the current ones and returns the zoom. ```js const zoom = mediumZoom('[data-zoomable]', { background: '#BADA55' }) const clonedZoom = zoom.clone({ margin: 48 }) clonedZoom.getOptions() // => { background: '#BADA55', margin: 48, ... } ``` #### `on(type: string, listener: () => void, options?: boolean | AddEventListenerOptions): Zoom` Registers the listener on each target of the zoom. The same `options` as [`addEventListener`](https://developer.mozilla.org/docs/Web/API/EventTarget/addEventListener#Parameters) are used. ```js const zoom = mediumZoom('[data-zoomable]') zoom.on('closed', event => { // the image has been closed }) zoom.on( 'open', event => { // the image has been opened (tracked only once) }, { once: true } ) ``` The zoom object is accessible in `event.detail.zoom`. #### `off(type: string, listener: () => void, options?: boolean | AddEventListenerOptions): Zoom` Removes the previously registered listener on each target of the zoom. The same `options` as [`removeEventListener`](https://developer.mozilla.org/docs/Web/API/EventTarget/removeEventListener#Parameters) are used. ```js const zoom = mediumZoom('[data-zoomable]') function listener(event) { // ... } zoom.on('open', listener) // ... zoom.off('open', listener) ``` The zoom object is accessible in `event.detail.zoom`. #### `getOptions(): object` Returns the zoom options as an object. ```js const zoom = mediumZoom({ background: '#BADA55' }) zoom.getOptions() // => { background: '#BADA55', ... } ``` #### `getImages(): HTMLElement[]` Returns the images attached to the zoom as an array of [`HTMLElement`s](https://developer.mozilla.org/docs/Web/API/HTMLElement). ```js const zoom = mediumZoom('[data-zoomable]') zoom.getImages() // => [HTMLElement, HTMLElement] ``` #### `getZoomedImage(): HTMLElement` Returns the current zoomed image as an [`HTMLElement`](https://developer.mozilla.org/docs/Web/API/HTMLElement) or `null` if none. ```js const zoom = mediumZoom('[data-zoomable]') zoom.getZoomedImage() // => null zoom.open().then(() => { zoom.getZoomedImage() // => HTMLElement }) ``` ### Attributes #### `data-zoom-src` Specifies the high definition image to open on zoom. This image loads when the user clicks on the source image. ```html <img src="image-thumbnail.jpg" data-zoom-src="image-hd.jpg" alt="My image" /> ``` ### Events | Event | Description | | ------ | --------------------------------------------------- | | open | Fired immediately when the `open` method is called | | opened | Fired when the zoom has finished being animated | | close | Fired immediately when the `close` method is called | | closed | Fired when the zoom out has finished being animated | | detach | Fired when the `detach` method is called | | update | Fired when the `update` method is called | ```js const zoom = mediumZoom('[data-zoomable]') zoom.on('open', event => { // track when the image is zoomed }) ``` The zoom object is accessible in `event.detail.zoom`. ## Framework integrations Medium Zoom is a JavaScript library that can be used with any framework. Here are some integrations that you can use to get started quickly: - [React](./examples/react) - [React Markdown](./examples/react-markdown) - [Vue](./examples/vue) - [Svelte](./examples/svelte) ## Examples <details> <summary>Trigger a zoom from another element</summary> ```js const button = document.querySelector('[data-action="zoom"]') const zoom = mediumZoom('#image') button.addEventListener('click', () => zoom.open()) ``` </details> <details> <summary>Track an event (for analytics)</summary> You can use the `open` event to keep track of how many times a user interacts with your image. This can be useful if you want to gather some analytics on user engagement. ```js let counter = 0 const zoom = mediumZoom('#image-tracked') zoom.on('open', event => { console.log(`"${event.target.alt}" has been zoomed ${++counter} times`) }) ``` </details> <details> <summary>Detach a zoom once closed</summary> ```js const zoom = mediumZoom('[data-zoomable]') zoom.on('closed', () => zoom.detach(), { once: true }) ``` </details> <details> <summary>Attach jQuery elements</summary> jQuery elements are compatible with `medium-zoom` once converted to an array. ```js mediumZoom($('[data-zoomable]').toArray()) ``` </details> <details> <summary>Create a zoomable React component</summary> ```js import React, { useRef } from 'react' import mediumZoom from 'medium-zoom' export function ImageZoom({ options, ...props }) { const zoomRef = useRef(null) function getZoom() { if (zoomRef.current === null) { zoomRef.current = mediumZoom(options) } return zoomRef.current } function attachZoom(image) { const zoom = getZoom() if (image) { zoom.attach(image) } else { zoom.detach() } } return <img {...props} ref={attachZoom} /> } ``` </details> <br> You can see [more examples](examples/) including [React](examples/react) and [Vue](examples/vue), or check out the [storybook](https://medium-zoom.francoischalifour.com/storybook). ## Debugging ### The zoomed image is not visible The library doesn't provide a `z-index` value on the zoomed image to avoid conflicts with other frameworks. Some frameworks might specify a `z-index` for their elements, which makes the zoomed image not visible. If that's the case, you can provide a `z-index` value in your CSS: ```css .medium-zoom-overlay, .medium-zoom-image--opened { z-index: 999; } ``` ## Browser support | IE | Edge | Chrome | Firefox | Safari | | --------------- | --------------- | ------ | ------- | ------ | | 10<sup>\*</sup> | 12<sup>\*</sup> | 36 | 34 | 9 | <sup>\*</sup> _These browsers require a [`template` polyfill](https://github.com/webcomponents/template) when using [custom templates](docs/template.md)_. <blockquote> <p align="center"> Cross-browser testing is sponsored by </p> <p align="center"> <a href="https://www.browserstack.com"> <img src="https://user-images.githubusercontent.com/6137112/44587083-35987000-a7b2-11e8-8e0d-8ba15de83802.png" alt="BrowserStack" height="35"> </a> </p> </blockquote> ## Contributing - Run `yarn` to install Node dev dependencies - Run `yarn start` to build the library in watch mode - Run `yarn run storybook` to see your changes at http://localhost:9001 Please read the [contributing guidelines](CONTRIBUTING.md) for more detailed explanations. _You can also use [npm](https://www.npmjs.com)._ ## License MIT Β© [FranΓ§ois Chalifour](https://francoischalifour.com) ", Assign "at most 3 tags" to the expected json: {"id":"5379","tags":[]} "only from the tags list I provide: [{"id":77,"name":"3d"},{"id":89,"name":"agent"},{"id":17,"name":"ai"},{"id":54,"name":"algorithm"},{"id":24,"name":"api"},{"id":44,"name":"authentication"},{"id":3,"name":"aws"},{"id":27,"name":"backend"},{"id":60,"name":"benchmark"},{"id":72,"name":"best-practices"},{"id":39,"name":"bitcoin"},{"id":37,"name":"blockchain"},{"id":1,"name":"blog"},{"id":45,"name":"bundler"},{"id":58,"name":"cache"},{"id":21,"name":"chat"},{"id":49,"name":"cicd"},{"id":4,"name":"cli"},{"id":64,"name":"cloud-native"},{"id":48,"name":"cms"},{"id":61,"name":"compiler"},{"id":68,"name":"containerization"},{"id":92,"name":"crm"},{"id":34,"name":"data"},{"id":47,"name":"database"},{"id":8,"name":"declarative-gui "},{"id":9,"name":"deploy-tool"},{"id":53,"name":"desktop-app"},{"id":6,"name":"dev-exp-lib"},{"id":59,"name":"dev-tool"},{"id":13,"name":"ecommerce"},{"id":26,"name":"editor"},{"id":66,"name":"emulator"},{"id":62,"name":"filesystem"},{"id":80,"name":"finance"},{"id":15,"name":"firmware"},{"id":73,"name":"for-fun"},{"id":2,"name":"framework"},{"id":11,"name":"frontend"},{"id":22,"name":"game"},{"id":81,"name":"game-engine "},{"id":23,"name":"graphql"},{"id":84,"name":"gui"},{"id":91,"name":"http"},{"id":5,"name":"http-client"},{"id":51,"name":"iac"},{"id":30,"name":"ide"},{"id":78,"name":"iot"},{"id":40,"name":"json"},{"id":83,"name":"julian"},{"id":38,"name":"k8s"},{"id":31,"name":"language"},{"id":10,"name":"learning-resource"},{"id":33,"name":"lib"},{"id":41,"name":"linter"},{"id":28,"name":"lms"},{"id":16,"name":"logging"},{"id":76,"name":"low-code"},{"id":90,"name":"message-queue"},{"id":42,"name":"mobile-app"},{"id":18,"name":"monitoring"},{"id":36,"name":"networking"},{"id":7,"name":"node-version"},{"id":55,"name":"nosql"},{"id":57,"name":"observability"},{"id":46,"name":"orm"},{"id":52,"name":"os"},{"id":14,"name":"parser"},{"id":74,"name":"react"},{"id":82,"name":"real-time"},{"id":56,"name":"robot"},{"id":65,"name":"runtime"},{"id":32,"name":"sdk"},{"id":71,"name":"search"},{"id":63,"name":"secrets"},{"id":25,"name":"security"},{"id":85,"name":"server"},{"id":86,"name":"serverless"},{"id":70,"name":"storage"},{"id":75,"name":"system-design"},{"id":79,"name":"terminal"},{"id":29,"name":"testing"},{"id":12,"name":"ui"},{"id":50,"name":"ux"},{"id":88,"name":"video"},{"id":20,"name":"web-app"},{"id":35,"name":"web-server"},{"id":43,"name":"webassembly"},{"id":69,"name":"workflow"},{"id":87,"name":"yaml"}]" returns me the "expected json"