master f6bf9966f0c5 cached
14 files
22.3 KB
6.6k tokens
8 symbols
1 requests
Download .txt
Repository: spinningarrow/zoom-vanilla.js
Branch: master
Commit: f6bf9966f0c5
Files: 14
Total size: 22.3 KB

Directory structure:
gitextract_wqs6ohsn/

├── .editorconfig
├── .envrc
├── .gitignore
├── .npmignore
├── .tern-project
├── ISSUE_TEMPLATE.md
├── LICENSE
├── README.md
├── css/
│   └── zoom.css
├── default.nix
├── dist/
│   └── zoom.css
├── index.html
├── js/
│   └── zoom-vanilla.js
└── package.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
[package.json]
indent_style = space
indent_size = 2

[*.js]
indent_style = tab
indent_size = 4


================================================
FILE: .envrc
================================================
use nix


================================================
FILE: .gitignore
================================================
node_modules


================================================
FILE: .npmignore
================================================
img
js
index.html
.editorconfig
.tern-project
default.nix
.envrc


================================================
FILE: .tern-project
================================================
{
	"libs": [
		"browser"
	]
}


================================================
FILE: ISSUE_TEMPLATE.md
================================================
<!--
Found an Issue?

Thanks for reporting it! If this looks like a bug it would be super helpful if
you could create a sample on CodePen to help me reproduce it.

Here is a template pen: https://codepen.io/spinningarrow/pen/WJprrg

It includes the necessary CSS and JS along with a sample image. Please fork it
in a way that demonstrates the issue, and paste the link in the issue
description.
-->


================================================
FILE: LICENSE
================================================
The MIT License

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


================================================
FILE: README.md
================================================
# zoom-vanilla.js [![npm version](https://badge.fury.io/js/zoom-vanilla.js.svg)](https://www.npmjs.com/package/zoom-vanilla.js)

```
                                                  _ _ _         _     
                                                 (_) | |       (_)    
  _______   ___  _ __ ___ ________   ____ _ _ __  _| | | __ _   _ ___ 
 |_  / _ \ / _ \| '_ ` _ \______\ \ / / _` | '_ \| | | |/ _` | | / __|
  / / (_) | (_) | | | | | |      \ V / (_| | | | | | | | (_| |_| \__ \
 /___\___/ \___/|_| |_| |_|       \_/ \__,_|_| |_|_|_|_|\__,_(_) |___/
                                                              _/ |    
                                                             |__/     
```

**Live demo**: [zoom-vanilla.js in action][live demo].

A simple library for image zooming; [as seen on Medium][medium-zoom-article].
It zooms in really smoothly, and zooms out when you click again, scroll away,
or press the <kbd>esc</kbd> key.

If you hold the <kbd>⌘</kbd> or <kbd>Ctrl</kbd> key when clicking the image, it
will open the image in a new tab instead of zooming it.

_This is a fork of the [jQuery plugin by fat][fat-zoom]_. These are the key
differences:

1. **No jQuery dependency**; vanilla JavaScript only
2. ~Equivalent~smaller file size (the minified version is slightly smaller due
   to better minification)
3. Includes bug fixes not present in [fat/zoom.js][fat-zoom], which is no
   longer being maintained

## Installation

1. Download the JS and CSS files using any of the following methods:    

    - npm: `npm i zoom-vanilla.js`. This will download the the necessary
	  files to the `node_modules/zoom-vanilla.js/dist/` directory.

    - Directly link to the files hosted on a CDN:
    
		- JS:
		  https://cdn.jsdelivr.net/npm/zoom-vanilla.js/dist/zoom-vanilla.min.js
        
        - CSS: https://cdn.jsdelivr.net/npm/zoom-vanilla.js/dist/zoom.css
    
	- Manually download `dist/zoom-vanilla.min.js` and `dist/zoom.css` from
	  GitHub

2. Add the `zoom-vanilla.min.js` and `zoom.css` files to your HTML page:

    ```html
    <!-- inside <head> -->
    <link href="path/to/dist/zoom.css" rel="stylesheet">

    <!-- before </body> -->
    <script src="path/to/dist/zoom-vanilla.min.js"></script>
    ```

    You can also `import` them if you're using webpack:

    ```javascript
    import "zoom-vanilla.js/dist/zoom.css"
    import "zoom-vanilla.js/dist/zoom-vanilla.min.js"
    ```

## Usage

Add a `data-action="zoom"` attribute to the images you want to make
zoomable:

```html
<img src="img/blog_post_featured.png" data-action="zoom">
```

## Browser support

zoom-vanilla.js should (in theory) work in all modern browsers. If not, create
an issue! Thanks!

[medium-zoom-article]: https://medium.com/designing-medium/image-zoom-on-medium-24d146fc0c20
[fat-zoom]: https://github.com/fat/zoom.js

## Known issues

- The image is appended to the body; use an appropriate CSS selector for extra
  styling
- Zooming may not be quite right if the aspect ratio of the image is changed

## Build

- `git clone` the repo
- `npm i` to install dev dependencies
- `npm start` to start a simple HTTP server (makes it easy to view the demo
  page)
- `npm run build` to build the minified JS and vendor-prefixed CSS
- `npm run watch` to rebuild when any JS files change (recommended for
  development)

[live demo]: http://code.sahil.me/zoom-vanilla.js


================================================
FILE: css/zoom.css
================================================
img[data-action="zoom"] {
  cursor: zoom-in;
}
.zoom-img,
.zoom-img-wrap {
  position: relative;
  z-index: 666;
  transition: all 300ms;
}
img.zoom-img {
  cursor: zoom-out;
}
.zoom-overlay {
  cursor: zoom-out;
  z-index: 420;
  background: #fff;
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  filter: "alpha(opacity=0)";
  opacity: 0;
  transition:      opacity 300ms;
}
.zoom-overlay-open .zoom-overlay {
  filter: "alpha(opacity=100)";
  opacity: 1;
}


================================================
FILE: default.nix
================================================
let pkgs = import <nixpkgs> {};

in pkgs.stdenv.mkDerivation rec {
  name = "zoom-vanilla.js";

  buildInputs = with pkgs; [
    nodejs-9_x
  ];
}


================================================
FILE: dist/zoom.css
================================================
img[data-action="zoom"] {
  cursor: zoom-in;
}
.zoom-img,
.zoom-img-wrap {
  position: relative;
  z-index: 666;
  transition: all 300ms;
}
img.zoom-img {
  cursor: zoom-out;
}
.zoom-overlay {
  cursor: zoom-out;
  z-index: 420;
  background: #fff;
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  filter: "alpha(opacity=0)";
  opacity: 0;
  transition:      opacity 300ms;
}
.zoom-overlay-open .zoom-overlay {
  filter: "alpha(opacity=100)";
  opacity: 1;
}

/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL2Nzcy96b29tLmNzcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTtFQUNFLGdCQUFnQjtDQUNqQjtBQUNEOztFQUVFLG1CQUFtQjtFQUNuQixhQUFhO0VBQ2Isc0JBQXNCO0NBQ3ZCO0FBQ0Q7RUFDRSxpQkFBaUI7Q0FDbEI7QUFDRDtFQUNFLGlCQUFpQjtFQUNqQixhQUFhO0VBQ2IsaUJBQWlCO0VBQ2pCLGdCQUFnQjtFQUNoQixPQUFPO0VBQ1AsUUFBUTtFQUNSLFNBQVM7RUFDVCxVQUFVO0VBQ1YsMkJBQTJCO0VBQzNCLFdBQVc7RUFDWCwrQkFBK0I7Q0FDaEM7QUFDRDtFQUNFLDZCQUE2QjtFQUM3QixXQUFXO0NBQ1oiLCJmaWxlIjoiem9vbS5jc3MiLCJzb3VyY2VzQ29udGVudCI6WyJpbWdbZGF0YS1hY3Rpb249XCJ6b29tXCJdIHtcbiAgY3Vyc29yOiB6b29tLWluO1xufVxuLnpvb20taW1nLFxuLnpvb20taW1nLXdyYXAge1xuICBwb3NpdGlvbjogcmVsYXRpdmU7XG4gIHotaW5kZXg6IDY2NjtcbiAgdHJhbnNpdGlvbjogYWxsIDMwMG1zO1xufVxuaW1nLnpvb20taW1nIHtcbiAgY3Vyc29yOiB6b29tLW91dDtcbn1cbi56b29tLW92ZXJsYXkge1xuICBjdXJzb3I6IHpvb20tb3V0O1xuICB6LWluZGV4OiA0MjA7XG4gIGJhY2tncm91bmQ6ICNmZmY7XG4gIHBvc2l0aW9uOiBmaXhlZDtcbiAgdG9wOiAwO1xuICBsZWZ0OiAwO1xuICByaWdodDogMDtcbiAgYm90dG9tOiAwO1xuICBmaWx0ZXI6IFwiYWxwaGEob3BhY2l0eT0wKVwiO1xuICBvcGFjaXR5OiAwO1xuICB0cmFuc2l0aW9uOiAgICAgIG9wYWNpdHkgMzAwbXM7XG59XG4uem9vbS1vdmVybGF5LW9wZW4gLnpvb20tb3ZlcmxheSB7XG4gIGZpbHRlcjogXCJhbHBoYShvcGFjaXR5PTEwMClcIjtcbiAgb3BhY2l0eTogMTtcbn1cbiJdfQ== */

================================================
FILE: index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>zoom-vanilla.js</title>

  <style>
    /* SIMPLE DEMO STYLES */
    body {
      font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
      font-size: 12px;
      line-height: 1.6;
    }
    .container {
      margin: 50px;
      max-width: 700px;
    }
    .container img {
      width: 100%;
    }
    .container .pull-left {
      width: 55%;
      float: left;
      margin: 20px 20px 20px -80px;
    }
    @media (min-width: 750px) {
      body {
        font-size: 16px;
        line-height: 1.6;
      }
      .container {
        margin: 100px auto;
      }
    }
  </style>
  <link rel="stylesheet" type="text/css" href="dist/zoom.css">
</head>
<body>
  <div class="container">
    <h1>Image Zoom</h1>
    <p>
      Trust fund seitan chia, wolf lomo letterpress Bushwick before they sold out. Carles kogi fixie, squid twee Tonx readymade cred typewriter scenester locavore kale chips vegan organic. Meggings pug wolf Shoreditch typewriter skateboard. McSweeney's iPhone chillwave, food truck direct trade disrupt flannel irony tousled Cosby sweater single-origin coffee. Organic disrupt bicycle rights, tattooed messenger bag flannel craft beer fashion axe bitters. Readymade sartorial craft beer, quinoa sustainable butcher Marfa Echo Park Terry Richardson gluten-free flannel retro cred mlkshk banjo. Salvia 90's art party Blue Bottle, PBR&B cardigan 8-bit.
    </p>
    <p>
      Meggings irony fashion axe, tattooed master cleanse Blue Bottle stumptown bitters authentic flannel freegan paleo letterpress ugh sriracha. Wolf PBR&B art party aesthetic meh cliche. Sartorial before they sold out deep v, aesthetic PBR&B craft beer post-ironic synth keytar pork belly skateboard pour-over. Tonx cray pug Etsy, gastropub ennui wolf ethnic Odd Future viral master cleanse skateboard banjo. Pitchfork scenester cornhole, whatever try-hard ethnic banjo +1 gastropub American Apparel vinyl skateboard Shoreditch seitan. Blue Bottle keffiyeh Austin Helvetica art party. Portland ethnic fixie, beard retro direct trade ugh scenester Tumblr readymade authentic plaid pickled hashtag biodiesel.
    </p>
    <img src="img/palm.jpg" data-action="zoom">
    <p>
      Thundercats freegan Truffaut, four loko twee Austin scenester lo-fi seitan High Life paleo quinoa cray. Schlitz butcher ethical Tumblr, pop-up DIY keytar ethnic iPhone PBR sriracha. Tonx direct trade bicycle rights gluten-free flexitarian asymmetrical. Whatever drinking vinegar PBR XOXO Bushwick gentrify. Cliche semiotics banjo retro squid Wes Anderson. Fashion axe dreamcatcher you probably haven't heard of them bicycle rights. Tote bag organic four loko ethical selfies gastropub, PBR fingerstache tattooed bicycle rights.
    </p>
    <p>
      Ugh Portland Austin, distillery tattooed typewriter polaroid pug Banksy Neutra keffiyeh. Shoreditch mixtape wolf PBR&B, tote bag dreamcatcher literally bespoke Odd Future selfies 90's master cleanse vegan. Flannel tofu deep v next level pickled, authentic Etsy Shoreditch literally swag photo booth iPhone pug semiotics banjo. Bicycle rights butcher Blue Bottle, actually DIY semiotics Banksy banjo mixtape Austin pork belly post-ironic. American Apparel gastropub hashtag, McSweeney's master cleanse occupy High Life bitters wayfarers next level bicycle rights. Wolf chia Terry Richardson, pop-up plaid kitsch ugh. Butcher +1 Carles, swag selfies Blue Bottle viral.
    </p>
    <p>
      Keffiyeh food truck organic letterpress leggings iPhone four loko hella pour-over occupy, Wes Anderson cray post-ironic. Neutra retro fixie gastropub +1, High Life semiotics. Vinyl distillery Etsy freegan flexitarian cliche jean shorts, Schlitz wayfarers skateboard tousled irony locavore XOXO meh. Ethnic Wes Anderson McSweeney's messenger bag, mixtape XOXO slow-carb cornhole aesthetic Marfa banjo Thundercats bitters. Raw denim banjo typewriter cray Tumblr, High Life single-origin coffee. 90's Tumblr cred, Terry Richardson occupy raw denim tofu fashion axe photo booth banh mi. Trust fund locavore Helvetica, fashion axe selvage authentic Shoreditch swag selfies stumptown +1.
    </p>
      <img src="img/trees.jpg" data-action="zoom" class="pull-left">
    <p>
      Scenester chambray slow-carb, trust fund biodiesel ugh bicycle rights cornhole. Gentrify messenger bag Truffaut tousled roof party pork belly leggings, photo booth jean shorts. Austin readymade PBR plaid chambray. Squid Echo Park pour-over, wayfarers forage whatever locavore typewriter artisan deep v four loko. Locavore occupy Neutra Pitchfork McSweeney's, wayfarers fingerstache. Actually asymmetrical drinking vinegar yr brunch biodiesel. Before they sold out sustainable readymade craft beer Portland gastropub squid Austin, roof party Thundercats chambray narwhal Bushwick pug.
    </p>
    <p>
      Literally typewriter chillwave, bicycle rights Carles flannel wayfarers. Biodiesel farm-to-table actually, locavore keffiyeh hella shabby chic pour-over try-hard Bushwick. Sriracha American Apparel Brooklyn, synth cray stumptown blog Bushwick +1 VHS hashtag. Wolf umami Carles Marfa, 90's food truck Cosby sweater. Fanny pack try-hard keytar pop-up readymade, master cleanse four loko trust fund polaroid salvia. Photo booth kitsch forage chambray, Carles scenester slow-carb lomo cardigan dreamcatcher. Swag asymmetrical leggings, biodiesel Tonx shabby chic ethnic master cleanse freegan.
    </p>
    <p>
      Raw denim Banksy shabby chic, 8-bit salvia narwhal fashion axe. Ethical Williamsburg four loko, chia kale chips distillery Shoreditch messenger bag swag iPhone Pitchfork. Viral PBR&B single-origin coffee quinoa readymade, ethical chillwave drinking vinegar gluten-free Wes Anderson kitsch Tumblr synth actually bitters. Butcher McSweeney's forage mlkshk kogi fingerstache. Selvage scenester butcher Shoreditch, Carles beard plaid disrupt DIY. Pug readymade selvage retro, Austin salvia vinyl master cleanse flexitarian deep v bicycle rights plaid Terry Richardson mlkshk pour-over. Trust fund try-hard banh mi Brooklyn, 90's Etsy kogi YOLO salvia.
    </p>

  </div>
  <script src="dist/zoom-vanilla.min.js"></script>
</body>
</html>


================================================
FILE: js/zoom-vanilla.js
================================================
+function () { "use strict";
	var OFFSET = 80

	// From http://youmightnotneedjquery.com/#offset
	function offset(element) {
		var rect = element.getBoundingClientRect()
		var scrollTop = window.pageYOffset ||
			document.documentElement.scrollTop ||
			document.body.scrollTop ||
			0
		var scrollLeft = window.pageXOffset ||
			document.documentElement.scrollLeft ||
			document.body.scrollLeft ||
			0
		return {
			top: rect.top + scrollTop,
			left: rect.left + scrollLeft
		}
	}

	function zoomListener() {
		var activeZoom = null
		var initialScrollPosition = null
		var initialTouchPosition = null

		function listen() {
			document.body.addEventListener('click', function (event) {
				if (event.target.getAttribute('data-action') !== 'zoom' ||
					event.target.tagName !== 'IMG') return

				zoom(event)
			})
		}

		function zoom(event) {
			event.stopPropagation()

			if (document.body.classList.contains('zoom-overlay-open')) return

			if (event.metaKey || event.ctrlKey) return openInNewWindow()

			closeActiveZoom({ forceDispose: true })

			activeZoom = vanillaZoom(event.target)
			activeZoom.zoomImage()

			addCloseActiveZoomListeners()
		}

		function openInNewWindow() {
			window.open(event.target.getAttribute('data-original') ||
				event.target.currentSrc ||
				event.target.src,
				'_blank')
		}

		function closeActiveZoom(options) {
			options = options || { forceDispose: false }
			if (!activeZoom) return

			activeZoom[options.forceDispose ? 'dispose' : 'close']()
			removeCloseActiveZoomListeners()
			activeZoom = null
		}

		function addCloseActiveZoomListeners() {
			// todo(fat): probably worth throttling this
			window.addEventListener('scroll', handleScroll)
			document.addEventListener('click', handleClick)
			document.addEventListener('keyup', handleEscPressed)
			document.addEventListener('touchstart', handleTouchStart)
			document.addEventListener('touchend', handleClick)
		}

		function removeCloseActiveZoomListeners() {
			window.removeEventListener('scroll', handleScroll)
			document.removeEventListener('keyup', handleEscPressed)
			document.removeEventListener('click', handleClick)
			document.removeEventListener('touchstart', handleTouchStart)
			document.removeEventListener('touchend', handleClick)
		}

		function handleScroll(event) {
			if (initialScrollPosition === null) initialScrollPosition = window.pageYOffset
			var deltaY = initialScrollPosition - window.pageYOffset
			if (Math.abs(deltaY) >= 40) closeActiveZoom()
		}

		function handleEscPressed(event) {
			if (event.keyCode == 27) closeActiveZoom()
		}

		function handleClick(event) {
			event.stopPropagation()
			event.preventDefault()
			closeActiveZoom()
		}

		function handleTouchStart(event) {
			initialTouchPosition = event.touches[0].pageY
			event.target.addEventListener('touchmove', handleTouchMove)
		}

		function handleTouchMove(event) {
			if (Math.abs(event.touches[0].pageY - initialTouchPosition) <= 10) return
			closeActiveZoom()
			event.target.removeEventListener('touchmove', handleTouchMove)
		}

		return { listen: listen }
	}

	var vanillaZoom = (function () {
		var fullHeight = null
		var fullWidth = null
		var overlay = null
		var imgScaleFactor = null

		var targetImage = null
		var targetImageWrap = null
		var targetImageClone = null

		function zoomImage() {
			var img = document.createElement('img')
			img.onload = function () {
				fullHeight = Number(img.height)
				fullWidth = Number(img.width)
				zoomOriginal()
			}
			img.src = targetImage.currentSrc || targetImage.src
		}

		function zoomOriginal() {
			targetImageWrap = document.createElement('div')
			targetImageWrap.className = 'zoom-img-wrap'
			targetImageWrap.style.position = 'absolute'
			targetImageWrap.style.top = offset(targetImage).top + 'px'
			targetImageWrap.style.left = offset(targetImage).left + 'px'

			targetImageClone = targetImage.cloneNode()
			targetImageClone.style.visibility = 'hidden'

			targetImage.style.width = targetImage.offsetWidth + 'px'
			targetImage.parentNode.replaceChild(targetImageClone, targetImage)

			document.body.appendChild(targetImageWrap)
			targetImageWrap.appendChild(targetImage)

			targetImage.classList.add('zoom-img')
			targetImage.setAttribute('data-action', 'zoom-out')

			overlay = document.createElement('div')
			overlay.className = 'zoom-overlay'

			document.body.appendChild(overlay)

			calculateZoom()
			triggerAnimation()
		}

		function calculateZoom() {
			targetImage.offsetWidth // repaint before animating

			var originalFullImageWidth  = fullWidth
			var originalFullImageHeight = fullHeight

			var maxScaleFactor = originalFullImageWidth / targetImage.width

			var viewportHeight = window.innerHeight - OFFSET
			var viewportWidth  = window.innerWidth - OFFSET

			var imageAspectRatio    = originalFullImageWidth / originalFullImageHeight
			var viewportAspectRatio = viewportWidth / viewportHeight

			if (originalFullImageWidth < viewportWidth && originalFullImageHeight < viewportHeight) {
				imgScaleFactor = maxScaleFactor
			} else if (imageAspectRatio < viewportAspectRatio) {
				imgScaleFactor = (viewportHeight / originalFullImageHeight) * maxScaleFactor
			} else {
				imgScaleFactor = (viewportWidth / originalFullImageWidth) * maxScaleFactor
			}
		}

		function triggerAnimation() {
			targetImage.offsetWidth // repaint before animating

			var imageOffset = offset(targetImage)
			var scrollTop   = window.pageYOffset

			var viewportY = scrollTop + (window.innerHeight / 2)
			var viewportX = (window.innerWidth / 2)

			var imageCenterY = imageOffset.top + (targetImage.height / 2)
			var imageCenterX = imageOffset.left + (targetImage.width / 2)

			var translateY = Math.round(viewportY - imageCenterY)
			var translateX = Math.round(viewportX - imageCenterX)

			var targetImageTransform = 'scale(' + imgScaleFactor + ')'
			var targetImageWrapTransform =
				'translate(' + translateX + 'px, ' + translateY + 'px) translateZ(0)'

			targetImage.style.webkitTransform = targetImageTransform
			targetImage.style.msTransform = targetImageTransform
			targetImage.style.transform = targetImageTransform

			targetImageWrap.style.webkitTransform = targetImageWrapTransform
			targetImageWrap.style.msTransform = targetImageWrapTransform
			targetImageWrap.style.transform = targetImageWrapTransform

			document.body.classList.add('zoom-overlay-open')
		}

		function close() {
			document.body.classList.remove('zoom-overlay-open')
			document.body.classList.add('zoom-overlay-transitioning')

			targetImage.style.webkitTransform = ''
			targetImage.style.msTransform = ''
			targetImage.style.transform = ''

			targetImageWrap.style.webkitTransform = ''
			targetImageWrap.style.msTransform = ''
			targetImageWrap.style.transform = ''

			if (!'transition' in document.body.style) return dispose()

			targetImageWrap.addEventListener('transitionend', dispose)
			targetImageWrap.addEventListener('webkitTransitionEnd', dispose)
		}

		function dispose() {
			targetImage.removeEventListener('transitionend', dispose)
			targetImage.removeEventListener('webkitTransitionEnd', dispose)

			if (!targetImageWrap || !targetImageWrap.parentNode) return

			targetImage.classList.remove('zoom-img')
			targetImage.style.width = ''
			targetImage.setAttribute('data-action', 'zoom')

			targetImageClone.parentNode.replaceChild(targetImage, targetImageClone)
			targetImageWrap.parentNode.removeChild(targetImageWrap)
			overlay.parentNode.removeChild(overlay)

			document.body.classList.remove('zoom-overlay-transitioning')
		}

		return function (target) {
			targetImage = target
			return { zoomImage: zoomImage, close: close, dispose: dispose }
		}
	}())

	zoomListener().listen()
}()


================================================
FILE: package.json
================================================
{
  "name": "zoom-vanilla.js",
  "version": "2.0.6",
  "description": "Medium's image zoom in vanilla JS (no jQuery)",
  "main": "dist/zoom-vanilla.min.js",
  "repository": {
    "type": "git",
    "url": "git@github.com:spinningarrow/zoom-vanilla.js.git"
  },
  "keywords": [
    "zoom",
    "vanilla",
    "vanilla-js",
    "image"
  ],
  "author": "spinningarrow <sahil29@gmail.com>",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/spinningarrow/zoom-vanilla.js/issues"
  },
  "homepage": "https://github.com/spinningarrow/zoom-vanilla.js",
  "devDependencies": {
    "autoprefixer": "^6.7.7",
    "http-server": "^0.9.0",
    "postcss-cli": "^3.0.0",
    "uglify-js": "^2.7.3",
    "watch": "^0.19.2"
  },
  "scripts": {
    "_echo_done": "echo [`date`] \"\\033[1;32mzoom-vanilla.min.js rebuilt\\033[0m\"",
    "compress": "uglifyjs --compress --mangle --source-map=dist/zoom-vanilla.min.js.map --source-map-url='/dist/zoom-vanilla.min.js.map' --source-map-root='/' --preamble=\"// $npm_package_name - $npm_package_version ($npm_package_homepage)\" js/zoom-vanilla.js > dist/zoom-vanilla.min.js",
    "clean": "rm -rf dist/*",
    "build": "npm run clean && npm run compress && npm run css",
    "css": "postcss css/zoom.css --use autoprefixer -d dist/",
    "start": "http-server",
    "version": "npm run build && git add -u .",
    "watch": "watch 'npm run compress && npm run _echo_done' js --ignoreDotFiles"
  }
}
Download .txt
gitextract_wqs6ohsn/

├── .editorconfig
├── .envrc
├── .gitignore
├── .npmignore
├── .tern-project
├── ISSUE_TEMPLATE.md
├── LICENSE
├── README.md
├── css/
│   └── zoom.css
├── default.nix
├── dist/
│   └── zoom.css
├── index.html
├── js/
│   └── zoom-vanilla.js
└── package.json
Download .txt
SYMBOL INDEX (8 symbols across 1 files)

FILE: js/zoom-vanilla.js
  function offset (line 5) | function offset(element) {
  function zoomListener (line 21) | function zoomListener() {
  function zoomImage (line 123) | function zoomImage() {
  function zoomOriginal (line 133) | function zoomOriginal() {
  function calculateZoom (line 161) | function calculateZoom() {
  function triggerAnimation (line 184) | function triggerAnimation() {
  function close (line 214) | function close() {
  function dispose (line 232) | function dispose() {
Condensed preview — 14 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (25K chars).
[
  {
    "path": ".editorconfig",
    "chars": 95,
    "preview": "[package.json]\nindent_style = space\nindent_size = 2\n\n[*.js]\nindent_style = tab\nindent_size = 4\n"
  },
  {
    "path": ".envrc",
    "chars": 8,
    "preview": "use nix\n"
  },
  {
    "path": ".gitignore",
    "chars": 13,
    "preview": "node_modules\n"
  },
  {
    "path": ".npmignore",
    "chars": 65,
    "preview": "img\njs\nindex.html\n.editorconfig\n.tern-project\ndefault.nix\n.envrc\n"
  },
  {
    "path": ".tern-project",
    "chars": 30,
    "preview": "{\n\t\"libs\": [\n\t\t\"browser\"\n\t]\n}\n"
  },
  {
    "path": "ISSUE_TEMPLATE.md",
    "chars": 399,
    "preview": "<!--\nFound an Issue?\n\nThanks for reporting it! If this looks like a bug it would be super helpful if\nyou could create a "
  },
  {
    "path": "LICENSE",
    "chars": 1040,
    "preview": "The MIT License\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and assoc"
  },
  {
    "path": "README.md",
    "chars": 3386,
    "preview": "# zoom-vanilla.js [![npm version](https://badge.fury.io/js/zoom-vanilla.js.svg)](https://www.npmjs.com/package/zoom-vani"
  },
  {
    "path": "css/zoom.css",
    "chars": 477,
    "preview": "img[data-action=\"zoom\"] {\n  cursor: zoom-in;\n}\n.zoom-img,\n.zoom-img-wrap {\n  position: relative;\n  z-index: 666;\n  trans"
  },
  {
    "path": "default.nix",
    "chars": 147,
    "preview": "let pkgs = import <nixpkgs> {};\n\nin pkgs.stdenv.mkDerivation rec {\n  name = \"zoom-vanilla.js\";\n\n  buildInputs = with pkg"
  },
  {
    "path": "dist/zoom.css",
    "chars": 1707,
    "preview": "img[data-action=\"zoom\"] {\n  cursor: zoom-in;\n}\n.zoom-img,\n.zoom-img-wrap {\n  position: relative;\n  z-index: 666;\n  trans"
  },
  {
    "path": "index.html",
    "chars": 6260,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n  <title>"
  },
  {
    "path": "js/zoom-vanilla.js",
    "chars": 7734,
    "preview": "+function () { \"use strict\";\n\tvar OFFSET = 80\n\n\t// From http://youmightnotneedjquery.com/#offset\n\tfunction offset(elemen"
  },
  {
    "path": "package.json",
    "chars": 1439,
    "preview": "{\n  \"name\": \"zoom-vanilla.js\",\n  \"version\": \"2.0.6\",\n  \"description\": \"Medium's image zoom in vanilla JS (no jQuery)\",\n "
  }
]

About this extraction

This page contains the full source code of the spinningarrow/zoom-vanilla.js GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 14 files (22.3 KB), approximately 6.6k tokens, and a symbol index with 8 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!