Initial commit
This commit is contained in:
commit
263201d869
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
*.log
|
||||||
|
*.tgz
|
||||||
|
dist
|
||||||
|
node_modules
|
9
README.md
Normal file
9
README.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
## Install dev server packages
|
||||||
|
|
||||||
|
docker run --rm -w /app -v $PWD:/app -u $(id -u):$(id -u) node npm install
|
||||||
|
|
||||||
|
## Start the dev server
|
||||||
|
|
||||||
|
docker run --rm --init -it -w /app -v $PWD:/app -p 8080:8080 node node dev-server.js
|
||||||
|
|
||||||
|
Visit `localhost:8080` to view.
|
11
dev-server.js
Normal file
11
dev-server.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
require('esbuild-server')
|
||||||
|
.createServer(
|
||||||
|
{
|
||||||
|
bundle: true,
|
||||||
|
entryPoints: ['src/app.js'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
static: 'public',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.start();
|
2
index.js
Normal file
2
index.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export { default as pan } from './src/modules/pan.js';
|
||||||
|
export { default as zoom } from './src/modules/zoom.js';
|
431
package-lock.json
generated
Normal file
431
package-lock.json
generated
Normal file
@ -0,0 +1,431 @@
|
|||||||
|
{
|
||||||
|
"name": "app",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"devDependencies": {
|
||||||
|
"esbuild": "^0.20.2",
|
||||||
|
"esbuild-server": "^0.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"aix"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/android-arm": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/android-arm64": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/android-x64": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/darwin-arm64": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/darwin-x64": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/freebsd-arm64": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/freebsd-x64": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-arm": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-arm64": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-ia32": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-loong64": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==",
|
||||||
|
"cpu": [
|
||||||
|
"loong64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-mips64el": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==",
|
||||||
|
"cpu": [
|
||||||
|
"mips64el"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-ppc64": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-riscv64": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==",
|
||||||
|
"cpu": [
|
||||||
|
"riscv64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-s390x": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==",
|
||||||
|
"cpu": [
|
||||||
|
"s390x"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-x64": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/netbsd-x64": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"netbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/openbsd-x64": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"openbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/sunos-x64": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"sunos"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/win32-arm64": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/win32-ia32": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/win32-x64": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"bin": {
|
||||||
|
"esbuild": "bin/esbuild"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@esbuild/aix-ppc64": "0.20.2",
|
||||||
|
"@esbuild/android-arm": "0.20.2",
|
||||||
|
"@esbuild/android-arm64": "0.20.2",
|
||||||
|
"@esbuild/android-x64": "0.20.2",
|
||||||
|
"@esbuild/darwin-arm64": "0.20.2",
|
||||||
|
"@esbuild/darwin-x64": "0.20.2",
|
||||||
|
"@esbuild/freebsd-arm64": "0.20.2",
|
||||||
|
"@esbuild/freebsd-x64": "0.20.2",
|
||||||
|
"@esbuild/linux-arm": "0.20.2",
|
||||||
|
"@esbuild/linux-arm64": "0.20.2",
|
||||||
|
"@esbuild/linux-ia32": "0.20.2",
|
||||||
|
"@esbuild/linux-loong64": "0.20.2",
|
||||||
|
"@esbuild/linux-mips64el": "0.20.2",
|
||||||
|
"@esbuild/linux-ppc64": "0.20.2",
|
||||||
|
"@esbuild/linux-riscv64": "0.20.2",
|
||||||
|
"@esbuild/linux-s390x": "0.20.2",
|
||||||
|
"@esbuild/linux-x64": "0.20.2",
|
||||||
|
"@esbuild/netbsd-x64": "0.20.2",
|
||||||
|
"@esbuild/openbsd-x64": "0.20.2",
|
||||||
|
"@esbuild/sunos-x64": "0.20.2",
|
||||||
|
"@esbuild/win32-arm64": "0.20.2",
|
||||||
|
"@esbuild/win32-ia32": "0.20.2",
|
||||||
|
"@esbuild/win32-x64": "0.20.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild-server": {
|
||||||
|
"version": "0.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-server/-/esbuild-server-0.3.0.tgz",
|
||||||
|
"integrity": "sha512-8RuzIdM13gs7MyYwxn/c88nDdx086aREBvzWDk4G3cC7nudF8480OTrvAvanVmFZ9anDv9U4cRX/OKbladaRVA==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"esbuild": ">=0.17.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
package.json
Normal file
7
package.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"name": "svg-pan-zoom",
|
||||||
|
"devDependencies": {
|
||||||
|
"esbuild": "^0.20.2",
|
||||||
|
"esbuild-server": "^0.3.0"
|
||||||
|
}
|
||||||
|
}
|
82
public/image.svg
Normal file
82
public/image.svg
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||||
|
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg viewBox="0 0 400 300" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<style>
|
||||||
|
svg {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
circle, rect {
|
||||||
|
fill-opacity: 0.9;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script type="text/javascript">//<![CDATA[
|
||||||
|
const svgns = 'http://www.w3.org/2000/svg',
|
||||||
|
svg = document.querySelector('svg'),
|
||||||
|
{ width: vbWidth, height: vbHeight } = svg.viewBox.baseVal,
|
||||||
|
shapeCount = 100,
|
||||||
|
maxColorValue = 256,
|
||||||
|
circleRadius = { max: 45, min: 5 },
|
||||||
|
rectSideLength = { max: 95, min: 5 };
|
||||||
|
|
||||||
|
function getRandomInt(max) {
|
||||||
|
return Math.floor(Math.random() * max);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRandomPositiveInt(max) {
|
||||||
|
return getRandomInt(max) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRandomFillAndStrokeVals() {
|
||||||
|
const shadeFactor = Math.random(),
|
||||||
|
fill = ['r', 'g', 'b'].map(() => getRandomInt(maxColorValue)),
|
||||||
|
stroke = fill.map(v => Math.floor(v * shadeFactor));
|
||||||
|
|
||||||
|
return {
|
||||||
|
fill: fill,
|
||||||
|
stroke: stroke
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRandomCircle(fill, stroke) {
|
||||||
|
const el = document.createElementNS(svgns, 'circle'),
|
||||||
|
r = getRandomPositiveInt(circleRadius.max) + circleRadius.min;
|
||||||
|
|
||||||
|
el.setAttributeNS(null, 'cx', getRandomInt(vbWidth));
|
||||||
|
el.setAttributeNS(null, 'cy', getRandomInt(vbHeight));
|
||||||
|
el.setAttributeNS(null, 'r', r);
|
||||||
|
el.setAttributeNS(null, 'fill', fill);
|
||||||
|
el.setAttributeNS(null, 'stroke', stroke);
|
||||||
|
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRandomRect(fill, stroke) {
|
||||||
|
const el = document.createElementNS(svgns, 'rect'),
|
||||||
|
[width, height] = ['w', 'h'].map(() =>
|
||||||
|
getRandomPositiveInt(rectSideLength.max) + rectSideLength.min
|
||||||
|
);
|
||||||
|
|
||||||
|
el.setAttributeNS(null, 'x', getRandomInt(vbWidth));
|
||||||
|
el.setAttributeNS(null, 'y', getRandomInt(vbHeight));
|
||||||
|
el.setAttributeNS(null, 'width', width);
|
||||||
|
el.setAttributeNS(null, 'height', height);
|
||||||
|
el.setAttributeNS(null, 'fill', fill);
|
||||||
|
el.setAttributeNS(null, 'stroke', stroke);
|
||||||
|
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRandomShape({ fill: fillVals, stroke: strokeVals }) {
|
||||||
|
const shapes = [getRandomCircle, getRandomRect],
|
||||||
|
[fill, stroke] = [fillVals, strokeVals].map(v => `rgb(${v.join(', ')})`);
|
||||||
|
|
||||||
|
return shapes[getRandomInt(shapes.length)](fill, stroke);
|
||||||
|
}
|
||||||
|
|
||||||
|
[...Array(shapeCount)]
|
||||||
|
.map(() => getRandomFillAndStrokeVals())
|
||||||
|
.forEach(fillAndStrokeVal => svg.appendChild(getRandomShape(fillAndStrokeVal)));
|
||||||
|
//]]></script>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.6 KiB |
33
public/index.html
Normal file
33
public/index.html
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
|
<title>SVG Pan & Zoom Example</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
text-align: center;
|
||||||
|
max-width: 100vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
object {
|
||||||
|
max-height: 400px;
|
||||||
|
max-width: 100%;
|
||||||
|
background-color: lightsteelblue;
|
||||||
|
border: 1px solid steelblue;
|
||||||
|
touch-action: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Pan & Zoom an SVG Image with JavaScript</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Click and drag on the image to pan. Use the mouse wheel
|
||||||
|
to zoom in and out.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<object type="image/svg+xml" data="image.svg"></object>
|
||||||
|
<script src="app.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
18
src/app.js
Normal file
18
src/app.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import zoom from './modules/zoom.js';
|
||||||
|
import pan from './modules/pan.js';
|
||||||
|
|
||||||
|
window.addEventListener('load', function () {
|
||||||
|
const svg = document.querySelector('object').contentDocument.querySelector('svg');
|
||||||
|
|
||||||
|
svg.addEventListener('wheel', e => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
svg.setAttributeNS(null, 'viewBox', zoom(svg, e));
|
||||||
|
}, { passive: false });
|
||||||
|
|
||||||
|
svg.addEventListener('pointerdown', e => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
pan(svg, e);
|
||||||
|
}, { passive: false });
|
||||||
|
});
|
77
src/modules/pan.js
Normal file
77
src/modules/pan.js
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
const minDistanceThreshold = 5;
|
||||||
|
|
||||||
|
function distanceBetween({ x: x1, y: y1 }, { x: x2, y: y2 }) {
|
||||||
|
return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function minDistanceThresholdIsMet(startPt, endPt) {
|
||||||
|
return distanceBetween(startPt, endPt) >= minDistanceThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPositionChangeInLocalCoords(svg, startPt, endPt) {
|
||||||
|
const matrix = svg.getScreenCTM().inverse(),
|
||||||
|
localStartPt = startPt.matrixTransform(matrix),
|
||||||
|
localEndPt = endPt.matrixTransform(matrix);
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: localStartPt.x - localEndPt.x,
|
||||||
|
y: localStartPt.y - localEndPt.y
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopEventPropagationToChildren(svg, type) {
|
||||||
|
svg.addEventListener(type, e => e.stopPropagation(), { capture: true, once: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
function setToCurrentPointerCoords(point, e) {
|
||||||
|
point.x = e.clientX;
|
||||||
|
point.y = e.clientY;
|
||||||
|
|
||||||
|
return point;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPanCoords(svg, startPt, movePt, initialPos) {
|
||||||
|
const posChange = getPositionChangeInLocalCoords(svg, startPt, movePt);
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: initialPos.x + posChange.x,
|
||||||
|
y: initialPos.y + posChange.y
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function setViewBoxPosition(svg, { x, y }) {
|
||||||
|
const { width, height } = svg.viewBox.baseVal;
|
||||||
|
|
||||||
|
svg.setAttributeNS(null, 'viewBox', `${x} ${y} ${width} ${height}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function (svg, e) {
|
||||||
|
const { x, y } = svg.viewBox.baseVal,
|
||||||
|
startPt = setToCurrentPointerCoords(new DOMPoint(), e),
|
||||||
|
movePt = new DOMPoint();
|
||||||
|
|
||||||
|
let isPanning = false;
|
||||||
|
|
||||||
|
function pointerMove(e) {
|
||||||
|
setToCurrentPointerCoords(movePt, e);
|
||||||
|
|
||||||
|
if (!isPanning && minDistanceThresholdIsMet(startPt, movePt)) {
|
||||||
|
isPanning = true;
|
||||||
|
e.target.setPointerCapture(e.pointerId);
|
||||||
|
setToCurrentPointerCoords(startPt, e);
|
||||||
|
stopEventPropagationToChildren(svg, 'click');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPanning) {
|
||||||
|
setViewBoxPosition(svg, getPanCoords(svg, startPt, movePt, { x, y }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
svg.addEventListener('pointermove', pointerMove);
|
||||||
|
|
||||||
|
svg.addEventListener(
|
||||||
|
'pointerup',
|
||||||
|
() => svg.removeEventListener('pointermove', pointerMove),
|
||||||
|
{ once: true }
|
||||||
|
);
|
||||||
|
}
|
57
src/modules/zoom.js
Normal file
57
src/modules/zoom.js
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
const zoomStepRatio = 0.25,
|
||||||
|
positive = 1,
|
||||||
|
negative = -1;
|
||||||
|
|
||||||
|
function toLocalCoords(svg, x, y) {
|
||||||
|
const clientP = new DOMPoint(x, y);
|
||||||
|
|
||||||
|
return clientP.matrixTransform(svg.getScreenCTM().inverse());
|
||||||
|
}
|
||||||
|
|
||||||
|
function zoomIn(deltaY) {
|
||||||
|
return deltaY < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function calcSizeChangeAmounts(width, height) {
|
||||||
|
return {
|
||||||
|
width: width * zoomStepRatio,
|
||||||
|
height: height * zoomStepRatio
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function calcValChangeRatios(focusPoint, x, y, width, height) {
|
||||||
|
return {
|
||||||
|
x: (focusPoint.x - x) / width,
|
||||||
|
y: (focusPoint.y - y) / height,
|
||||||
|
width: (width + x - focusPoint.x) / width,
|
||||||
|
height: (height + y - focusPoint.y) / height
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function calcValChangeAmounts(focusPoint, x, y, width, height) {
|
||||||
|
const changeAmount = calcSizeChangeAmounts(width, height),
|
||||||
|
valChangeRatio = calcValChangeRatios(focusPoint, x, y, width, height);
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: valChangeRatio.x * changeAmount.width,
|
||||||
|
y: valChangeRatio.y * changeAmount.height,
|
||||||
|
width: valChangeRatio.width * changeAmount.width,
|
||||||
|
height: valChangeRatio.height * changeAmount.height
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function (svg, e) {
|
||||||
|
const pointerPosition = toLocalCoords(svg, e.clientX, e.clientY),
|
||||||
|
sign = zoomIn(e.deltaY) ? positive : negative,
|
||||||
|
{ x, y, width, height } = svg.viewBox.baseVal,
|
||||||
|
changeAmount = calcValChangeAmounts(pointerPosition, x, y, width, height),
|
||||||
|
|
||||||
|
attr = {
|
||||||
|
x: x + sign * changeAmount.x,
|
||||||
|
y: y + sign * changeAmount.y,
|
||||||
|
width: width + sign * (-changeAmount.x - changeAmount.width),
|
||||||
|
height: height + sign * (-changeAmount.y - changeAmount.height)
|
||||||
|
};
|
||||||
|
|
||||||
|
return `${attr.x} ${attr.y} ${attr.width} ${attr.height}`;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user