Add magnification when editing pet appearance
I couldn't find a library for this functionality that didn't require jQuery, and I don't want to be adding *more* jQuery requirements. So, I decided to throw together my own! The `<magic-magnifier>` component copies its contents into a "lens" element, then uses basic JS to track mouse position, then uses CSS to move the lens and its contents into a helpful position. One thing I noticed here is that the zoom is a bit crunchy because we're using PNG images, and it's hard to zoom in even further than we already are. I might try switching this UI to use the SVG images by default instead?
This commit is contained in:
parent
b3f3b39aa0
commit
b2a23b3e7b
6 changed files with 91 additions and 5 deletions
33
app/assets/javascripts/magic-magnifier.js
Normal file
33
app/assets/javascripts/magic-magnifier.js
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
class MagicMagnifier extends HTMLElement {
|
||||||
|
connectedCallback() {
|
||||||
|
setTimeout(() => this.#attachLens(), 0);
|
||||||
|
this.addEventListener("mousemove", this.#onMouseMove);
|
||||||
|
}
|
||||||
|
|
||||||
|
#attachLens() {
|
||||||
|
const lens = document.createElement("magic-magnifier-lens");
|
||||||
|
lens.inert = true;
|
||||||
|
lens.useContent(this.children);
|
||||||
|
this.appendChild(lens);
|
||||||
|
}
|
||||||
|
|
||||||
|
#onMouseMove(e) {
|
||||||
|
const lens = this.querySelector("magic-magnifier-lens");
|
||||||
|
const rect = this.getBoundingClientRect();
|
||||||
|
const x = e.pageX - rect.left;
|
||||||
|
const y = e.pageY - rect.top;
|
||||||
|
lens.style.setProperty("--magic-magnifier-x", x + "px");
|
||||||
|
lens.style.setProperty("--magic-magnifier-y", y + "px");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MagicMagnifierLens extends HTMLElement {
|
||||||
|
useContent(contentNodes) {
|
||||||
|
for (const contentNode of contentNodes) {
|
||||||
|
this.appendChild(contentNode.cloneNode(true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("magic-magnifier", MagicMagnifier);
|
||||||
|
customElements.define("magic-magnifier-lens", MagicMagnifierLens);
|
|
@ -13,10 +13,12 @@ class SupportOutfitViewer extends HTMLElement {
|
||||||
if (!e.target.matches("tr")) return;
|
if (!e.target.matches("tr")) return;
|
||||||
|
|
||||||
const id = e.target.querySelector("[data-field=id]").innerText;
|
const id = e.target.querySelector("[data-field=id]").innerText;
|
||||||
const layer = this.querySelector(
|
const layers = this.querySelectorAll(
|
||||||
`outfit-viewer [data-asset-id="${CSS.escape(id)}"]`,
|
`outfit-viewer [data-asset-id="${CSS.escape(id)}"]`,
|
||||||
);
|
);
|
||||||
layer.setAttribute("highlighted", "");
|
for (const layer of layers) {
|
||||||
|
layer.setAttribute("highlighted", "");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// When a row is unhovered, unhighlight its corresponding outfit viewer layer.
|
// When a row is unhovered, unhighlight its corresponding outfit viewer layer.
|
||||||
|
@ -24,10 +26,12 @@ class SupportOutfitViewer extends HTMLElement {
|
||||||
if (!e.target.matches("tr")) return;
|
if (!e.target.matches("tr")) return;
|
||||||
|
|
||||||
const id = e.target.querySelector("[data-field=id]").innerText;
|
const id = e.target.querySelector("[data-field=id]").innerText;
|
||||||
const layer = this.querySelector(
|
const layers = this.querySelectorAll(
|
||||||
`outfit-viewer [data-asset-id="${CSS.escape(id)}"]`,
|
`outfit-viewer [data-asset-id="${CSS.escape(id)}"]`,
|
||||||
);
|
);
|
||||||
layer.removeAttribute("highlighted");
|
for (const layer of layers) {
|
||||||
|
layer.removeAttribute("highlighted");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// When clicking a row, redirect the click to the first link.
|
// When clicking a row, redirect the click to the first link.
|
||||||
|
|
40
app/assets/stylesheets/application/magic-magnifier.sass
Normal file
40
app/assets/stylesheets/application/magic-magnifier.sass
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
magic-magnifier
|
||||||
|
position: relative
|
||||||
|
|
||||||
|
&:not(:hover)
|
||||||
|
magic-magnifier-lens
|
||||||
|
display: none
|
||||||
|
|
||||||
|
magic-magnifier-lens
|
||||||
|
width: var(--magic-magnifier-lens-width, 100px)
|
||||||
|
height: var(--magic-magnifier-lens-height, 100px)
|
||||||
|
overflow: hidden
|
||||||
|
border-radius: 100%
|
||||||
|
|
||||||
|
background: white
|
||||||
|
border: 2px solid black
|
||||||
|
box-shadow: 3px 3px 3px rgba(0, 0, 0, .5)
|
||||||
|
|
||||||
|
position: absolute
|
||||||
|
left: var(--magic-magnifier-x, 0px)
|
||||||
|
top: var(--magic-magnifier-y, 0px)
|
||||||
|
|
||||||
|
> *
|
||||||
|
// Translations are applied in the opposite of the order they're specified.
|
||||||
|
// So, here's what we're doing:
|
||||||
|
//
|
||||||
|
// 1. Translate the content left by --magic-magnifier-x and up by
|
||||||
|
// --magic-magnifier-y, to align the target location with the lens's
|
||||||
|
// top-right corner.
|
||||||
|
// 2. Zoom in by --magic-magnifier-scale.
|
||||||
|
// 3. Translate the content right by half of --magic-magnifier-lens-width,
|
||||||
|
// and down by half of --magic-magnifier-lens-height, to align the
|
||||||
|
// target location with the lens's center.
|
||||||
|
//
|
||||||
|
// Note that it *is* possible to specify transforms relative to the center,
|
||||||
|
// rather than the top-left corner—this is in fact the default!—but that
|
||||||
|
// gets confusing fast with scale in play. I think this is easier to reason
|
||||||
|
// about with the top-left corner in terms of math, and center it after the
|
||||||
|
// fact.
|
||||||
|
transform: translateX(calc(var(--magic-magnifier-lens-width, 100px) / 2)) translateY(calc(var(--magic-magnifier-lens-height, 100px) / 2)) scale(var(--magic-magnifier-scale, 2)) translateX(calc(-1 * var(--magic-magnifier-x, 0px))) translateY(calc(-1 * var(--magic-magnifier-y, 0px)))
|
||||||
|
transform-origin: left top
|
|
@ -37,3 +37,8 @@ support-outfit-viewer
|
||||||
cursor: zoom-in
|
cursor: zoom-in
|
||||||
&:hover
|
&:hover
|
||||||
background: $module-bg-color
|
background: $module-bg-color
|
||||||
|
|
||||||
|
magic-magnifier
|
||||||
|
--magic-magnifier-lens-width: 100px
|
||||||
|
--magic-magnifier-lens-height: 100px
|
||||||
|
--magic-magnifier-scale: 2.5
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
= content_tag "support-outfit-viewer", **html_options do
|
= content_tag "support-outfit-viewer", **html_options do
|
||||||
= outfit_viewer outfit
|
%magic-magnifier
|
||||||
|
= outfit_viewer outfit
|
||||||
|
|
||||||
%table
|
%table
|
||||||
%thead
|
%thead
|
||||||
%tr
|
%tr
|
||||||
|
|
|
@ -45,11 +45,13 @@
|
||||||
|
|
||||||
- content_for :stylesheets do
|
- content_for :stylesheets do
|
||||||
= stylesheet_link_tag "application/breadcrumbs"
|
= stylesheet_link_tag "application/breadcrumbs"
|
||||||
|
= stylesheet_link_tag "application/magic-magnifier"
|
||||||
= stylesheet_link_tag "application/outfit-viewer"
|
= stylesheet_link_tag "application/outfit-viewer"
|
||||||
= stylesheet_link_tag "application/support-form"
|
= stylesheet_link_tag "application/support-form"
|
||||||
= stylesheet_link_tag "pet_states/support-outfit-viewer"
|
= stylesheet_link_tag "pet_states/support-outfit-viewer"
|
||||||
= page_stylesheet_link_tag "pet_states/edit"
|
= page_stylesheet_link_tag "pet_states/edit"
|
||||||
|
|
||||||
- content_for :javascripts do
|
- content_for :javascripts do
|
||||||
|
= javascript_include_tag "magic-magnifier"
|
||||||
= javascript_include_tag "outfit-viewer"
|
= javascript_include_tag "outfit-viewer"
|
||||||
= javascript_include_tag "pet_states/support-outfit-viewer"
|
= javascript_include_tag "pet_states/support-outfit-viewer"
|
||||||
|
|
Loading…
Reference in a new issue