From 5de5f1d1937b9a5c089c33852fb51c35714f7e82 Mon Sep 17 00:00:00 2001 From: Michael Lefrancois Date: Tue, 12 Oct 2021 13:10:35 +0200 Subject: [PATCH] initial --- .editorconfig | 12 +++++++ .gitignore | 2 ++ LICENSE.md | 8 +++++ README.md | 70 +++++++++++++++++++++++++++++++++++++ builds/cdn.js | 5 +++ builds/module.js | 3 ++ dist/cdn.js | 52 +++++++++++++++++++++++++++ dist/cdn.min.js | 1 + dist/module.cjs.js | 61 ++++++++++++++++++++++++++++++++ dist/module.esm.js | 51 +++++++++++++++++++++++++++ examples/index.html | 25 +++++++++++++ package-lock.json | 67 +++++++++++++++++++++++++++++++++++ package.json | 24 +++++++++++++ scripts/build.js | 85 +++++++++++++++++++++++++++++++++++++++++++++ src/index.js | 40 +++++++++++++++++++++ 15 files changed, 506 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 builds/cdn.js create mode 100644 builds/module.js create mode 100644 dist/cdn.js create mode 100644 dist/cdn.min.js create mode 100644 dist/module.cjs.js create mode 100644 dist/module.esm.js create mode 100644 examples/index.html create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 scripts/build.js create mode 100644 src/index.js diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..2e7acaf --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..76efb07 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +.vscode diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..df2e539 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,8 @@ +MIT License +Copyright (c) 2021 Michael Lefrancois + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..c62b65b --- /dev/null +++ b/README.md @@ -0,0 +1,70 @@ +# Alpine Focal + +Add focal point alignment of images to your Alpine 3.x components with a custom directive. + +![GitHub](https://img.shields.io/github/license/lefrancois/alpine-focal?color=red&style=flat-square) +![Build size Brotli](https://img.badgesize.io/lefrancois/alpine-focal/master/dist/cdn.min.js.svg?compression=gzip&style=flat-square&color=green) +[![](https://data.jsdelivr.com/v1/package/npm/@lefrancois/alpine-focal/badge)](https://www.jsdelivr.com/package/npm/@lefrancois/alpine-focal) +[![](https://img.shields.io/discord/492260310414262274.svg?color=7289da&label=lefrancois&logo=discord&style=flat-square)](https://discord.gg/fnBQBdVdSZ) +[![](https://img.shields.io/twitter/follow/lefrancois?style=flat-square)](https://www.twitter.com/lefrancois) + +> This package only supports Alpine v3.x. + +## About + +This plugin adds a new `x-focal` directive to Alpine that allows to easliy align your images to keep focus on the selected part of the image. + +## Installation + +### CDN + +Include the following ` +``` + +### NPM + +```bash +npm install @lefrancois/alpine-focal +``` + +Add the `x-focal` directive to your project by registering the plugin with Alpine. + +```js +import Alpine from "alpinejs"; +import Focal from "@lefrancois/alpine-focal"; + +Alpine.plugin(Focal); + +window.Alpine = Alpine; +window.Alpine.start(); +``` + +## Usage + +Attach the `x-focal` directive on an image element and enter the point (unsing px or % informations) to keep in focus while containing the image. + +```html +
+ + +
+``` + +Optinally you can use set the third parameter to true (`x-focal="100px 100px true"`) to get a preview image where you can set the focal point with an easy click on the image. Note: Just copy and paste the value to the `x-focal` attribute of the image element in production. ;) This is just a helper for your development. + +## License + +Copyright (c) 2021 Michael Lefrancois + +Licensed under the MIT license, see [LICENSE.md](LICENSE.md) for details. diff --git a/builds/cdn.js b/builds/cdn.js new file mode 100644 index 0000000..cd86bbf --- /dev/null +++ b/builds/cdn.js @@ -0,0 +1,5 @@ +import Focal from '../src/index' + +document.addEventListener('alpine:initializing', () => { + window.Alpine.plugin(Focal) +}) diff --git a/builds/module.js b/builds/module.js new file mode 100644 index 0000000..9e93aa4 --- /dev/null +++ b/builds/module.js @@ -0,0 +1,3 @@ +import Focal from '../src/index' + +export default Focal diff --git a/dist/cdn.js b/dist/cdn.js new file mode 100644 index 0000000..f1ff131 --- /dev/null +++ b/dist/cdn.js @@ -0,0 +1,52 @@ +(() => { + // src/index.js + function src_default(Alpine) { + Alpine.directive("focal", (el, {expression}) => { + const parts = expression.split(" "); + let xOffset = parts[0] || "50%"; + let yOffset = parts[1] || "50%"; + let edit = parts[2] || false; + if (edit) { + el.style["object-fit"] = ""; + el.onclick = (e) => { + const rect = el.getBoundingClientRect(); + const x = (e.clientX - rect.left) / rect.width; + const y = (e.clientY - rect.top) / rect.height; + const shiftCenterX = (rect.width / 2 - x * rect.width) / rect.width; + const shiftCenterY = (rect.height / 2 - y * rect.height) / rect.height; + xOffset = (x - shiftCenterX / 2) * 100; + yOffset = (y - shiftCenterY / 2) * 100; + if (xOffset > 100) + xOffset = 100; + if (xOffset < 0) + xOffset = 0; + if (yOffset > 100) + yOffset = 100; + if (yOffset < 0) + yOffset = 0; + xOffset = Math.round(xOffset); + yOffset = Math.round(yOffset); + const xPixelOffset = Math.round(xOffset / 100 * el.naturalWidth); + const yPixelOffset = Math.round(yOffset / 100 * el.naturalHeight); + el.style["object-fit"] = "cover"; + el.style["object-position"] = `${xOffset}% ${yOffset}%`; + alert(`Use ${xOffset}% ${yOffset}% or ${xPixelOffset}px ${yPixelOffset}px`); + }; + } else { + if (xOffset.endsWith("x")) { + xOffset = `${parseInt(xOffset) * 100 / el.naturalWidth}%`; + } + if (yOffset.endsWith("x")) { + yOffset = `${parseInt(yOffset) * 100 / el.naturalHeight}%`; + } + el.style["object-fit"] = "cover"; + el.style["object-position"] = `${xOffset} ${yOffset}`; + } + }); + } + + // builds/cdn.js + document.addEventListener("alpine:initializing", () => { + window.Alpine.plugin(src_default); + }); +})(); diff --git a/dist/cdn.min.js b/dist/cdn.min.js new file mode 100644 index 0000000..e6fce4c --- /dev/null +++ b/dist/cdn.min.js @@ -0,0 +1 @@ +(()=>{function h(r){r.directive("focal",(e,{expression:a})=>{let o=a.split(" "),t=o[0]||"50%",i=o[1]||"50%";o[2]||!1?(e.style["object-fit"]="",e.onclick=s=>{let n=e.getBoundingClientRect(),c=(s.clientX-n.left)/n.width,f=(s.clientY-n.top)/n.height,d=(n.width/2-c*n.width)/n.width,l=(n.height/2-f*n.height)/n.height;t=(c-d/2)*100,i=(f-l/2)*100,t>100&&(t=100),t<0&&(t=0),i>100&&(i=100),i<0&&(i=0),t=Math.round(t),i=Math.round(i);let p=Math.round(t/100*e.naturalWidth),u=Math.round(i/100*e.naturalHeight);e.style["object-fit"]="cover",e.style["object-position"]=`${t}% ${i}%`,alert(`Use ${t}% ${i}% or ${p}px ${u}px`)}):(t.endsWith("x")&&(t=`${parseInt(t)*100/e.naturalWidth}%`),i.endsWith("x")&&(i=`${parseInt(i)*100/e.naturalHeight}%`),e.style["object-fit"]="cover",e.style["object-position"]=`${t} ${i}`)})}document.addEventListener("alpine:initializing",()=>{window.Alpine.plugin(h)});})(); diff --git a/dist/module.cjs.js b/dist/module.cjs.js new file mode 100644 index 0000000..6a29e9b --- /dev/null +++ b/dist/module.cjs.js @@ -0,0 +1,61 @@ +var __defProp = Object.defineProperty; +var __markAsModule = (target) => __defProp(target, "__esModule", {value: true}); +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, {get: all[name], enumerable: true}); +}; + +// builds/module.js +__markAsModule(exports); +__export(exports, { + default: () => module_default +}); + +// src/index.js +function src_default(Alpine) { + Alpine.directive("focal", (el, {expression}) => { + const parts = expression.split(" "); + let xOffset = parts[0] || "50%"; + let yOffset = parts[1] || "50%"; + let edit = parts[2] || false; + if (edit) { + el.style["object-fit"] = ""; + el.onclick = (e) => { + const rect = el.getBoundingClientRect(); + const x = (e.clientX - rect.left) / rect.width; + const y = (e.clientY - rect.top) / rect.height; + const shiftCenterX = (rect.width / 2 - x * rect.width) / rect.width; + const shiftCenterY = (rect.height / 2 - y * rect.height) / rect.height; + xOffset = (x - shiftCenterX / 2) * 100; + yOffset = (y - shiftCenterY / 2) * 100; + if (xOffset > 100) + xOffset = 100; + if (xOffset < 0) + xOffset = 0; + if (yOffset > 100) + yOffset = 100; + if (yOffset < 0) + yOffset = 0; + xOffset = Math.round(xOffset); + yOffset = Math.round(yOffset); + const xPixelOffset = Math.round(xOffset / 100 * el.naturalWidth); + const yPixelOffset = Math.round(yOffset / 100 * el.naturalHeight); + el.style["object-fit"] = "cover"; + el.style["object-position"] = `${xOffset}% ${yOffset}%`; + alert(`Use ${xOffset}% ${yOffset}% or ${xPixelOffset}px ${yPixelOffset}px`); + }; + } else { + if (xOffset.endsWith("x")) { + xOffset = `${parseInt(xOffset) * 100 / el.naturalWidth}%`; + } + if (yOffset.endsWith("x")) { + yOffset = `${parseInt(yOffset) * 100 / el.naturalHeight}%`; + } + el.style["object-fit"] = "cover"; + el.style["object-position"] = `${xOffset} ${yOffset}`; + } + }); +} + +// builds/module.js +var module_default = src_default; diff --git a/dist/module.esm.js b/dist/module.esm.js new file mode 100644 index 0000000..c4af010 --- /dev/null +++ b/dist/module.esm.js @@ -0,0 +1,51 @@ +// src/index.js +function src_default(Alpine) { + Alpine.directive("focal", (el, {expression}) => { + const parts = expression.split(" "); + let xOffset = parts[0] || "50%"; + let yOffset = parts[1] || "50%"; + let edit = parts[2] || false; + if (edit) { + el.style["object-fit"] = ""; + el.onclick = (e) => { + const rect = el.getBoundingClientRect(); + const x = (e.clientX - rect.left) / rect.width; + const y = (e.clientY - rect.top) / rect.height; + const shiftCenterX = (rect.width / 2 - x * rect.width) / rect.width; + const shiftCenterY = (rect.height / 2 - y * rect.height) / rect.height; + xOffset = (x - shiftCenterX / 2) * 100; + yOffset = (y - shiftCenterY / 2) * 100; + if (xOffset > 100) + xOffset = 100; + if (xOffset < 0) + xOffset = 0; + if (yOffset > 100) + yOffset = 100; + if (yOffset < 0) + yOffset = 0; + xOffset = Math.round(xOffset); + yOffset = Math.round(yOffset); + const xPixelOffset = Math.round(xOffset / 100 * el.naturalWidth); + const yPixelOffset = Math.round(yOffset / 100 * el.naturalHeight); + el.style["object-fit"] = "cover"; + el.style["object-position"] = `${xOffset}% ${yOffset}%`; + alert(`Use ${xOffset}% ${yOffset}% or ${xPixelOffset}px ${yPixelOffset}px`); + }; + } else { + if (xOffset.endsWith("x")) { + xOffset = `${parseInt(xOffset) * 100 / el.naturalWidth}%`; + } + if (yOffset.endsWith("x")) { + yOffset = `${parseInt(yOffset) * 100 / el.naturalHeight}%`; + } + el.style["object-fit"] = "cover"; + el.style["object-position"] = `${xOffset} ${yOffset}`; + } + }); +} + +// builds/module.js +var module_default = src_default; +export { + module_default as default +}; diff --git a/examples/index.html b/examples/index.html new file mode 100644 index 0000000..960c3a2 --- /dev/null +++ b/examples/index.html @@ -0,0 +1,25 @@ + + + + + + Alpine.js Focal + + + + + + + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..3a1de1e --- /dev/null +++ b/package-lock.json @@ -0,0 +1,67 @@ +{ + "name": "@lefrancois/alpine-focal", + "version": "1.0.2", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "@lefrancois/alpine-focal", + "version": "1.0.0", + "devDependencies": { + "brotli-size": "^4.0.0", + "esbuild": "^0.8.39" + } + }, + "node_modules/brotli-size": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/brotli-size/-/brotli-size-4.0.0.tgz", + "integrity": "sha512-uA9fOtlTRC0iqKfzff1W34DXUA3GyVqbUaeo3Rw3d4gd1eavKVCETXrn3NzO74W+UVkG3UHu8WxUi+XvKI/huA==", + "dev": true, + "dependencies": { + "duplexer": "0.1.1" + }, + "engines": { + "node": ">= 10.16.0" + } + }, + "node_modules/duplexer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", + "dev": true + }, + "node_modules/esbuild": { + "version": "0.8.57", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.8.57.tgz", + "integrity": "sha512-j02SFrUwFTRUqiY0Kjplwjm1psuzO1d6AjaXKuOR9hrY0HuPsT6sV42B6myW34h1q4CRy+Y3g4RU/cGJeI/nNA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + } + } + }, + "dependencies": { + "brotli-size": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/brotli-size/-/brotli-size-4.0.0.tgz", + "integrity": "sha512-uA9fOtlTRC0iqKfzff1W34DXUA3GyVqbUaeo3Rw3d4gd1eavKVCETXrn3NzO74W+UVkG3UHu8WxUi+XvKI/huA==", + "dev": true, + "requires": { + "duplexer": "0.1.1" + } + }, + "duplexer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", + "dev": true + }, + "esbuild": { + "version": "0.8.57", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.8.57.tgz", + "integrity": "sha512-j02SFrUwFTRUqiY0Kjplwjm1psuzO1d6AjaXKuOR9hrY0HuPsT6sV42B6myW34h1q4CRy+Y3g4RU/cGJeI/nNA==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..757b4dc --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "@lefrancois/alpine-focal", + "description": "Add focal point image alignment to your Alpine 3.x components with a custom directive.", + "version": "1.0.0", + "author": { + "name": "Michael Lefrancois", + "email": "michael@lefrancois.de", + "url": "https://www.lefrancois.de" + }, + "repository": { + "type": "git", + "url": "https://github.com/lefrancois/alpine-focal" + }, + "main": "dist/module.cjs.js", + "module": "dist/module.esm.js", + "devDependencies": { + "brotli-size": "^4.0.0", + "esbuild": "^0.8.39" + }, + "scripts": { + "build": "node ./scripts/build.js", + "watch": "node ./scripts/build.js --watch" + } +} diff --git a/scripts/build.js b/scripts/build.js new file mode 100644 index 0000000..a27511b --- /dev/null +++ b/scripts/build.js @@ -0,0 +1,85 @@ +let fs = require('fs'); +let brotliSize = require('brotli-size'); + +(() => { + if (! fs.existsSync(`./dist`)) { + fs.mkdirSync(`./dist`, 0744); + } + + // Go through each file in the package's "build" directory + // and use the appropriate bundling strategy based on its name. + fs.readdirSync(`./builds`).forEach(file => { + bundleFile(file) + }); +})() + +function bundleFile(file) { + // Based on the filename, give esbuild a specific configuration to build. + ({ + // This output file is meant to be loaded in a browser's