hy-masonry
Version:
Animated Masonry Layout as Web Component - Living organisms that morph and breathe
3 lines (2 loc) • 16.5 kB
JavaScript
"use strict";var t=Object.defineProperty,e=(e,i,s)=>(((e,i,s)=>{i in e?t(e,i,{enumerable:!0,configurable:!0,writable:!0,value:s}):e[i]=s})(e,"symbol"!=typeof i?i+"":i,s),s);Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});class i{constructor(){e(this,"animations",new Map),e(this,"animationFrame",null),e(this,"isRunning",!1),e(this,"lastTime",0),this.start()}addAnimation(t,e,i={}){const s=this.createAnimation(e,i);this.animations.set(t,s)}removeAnimation(t){this.animations.delete(t)}createAnimation(t,e){switch(t){case"breathing":const t=new s(e);return(e,i)=>t.update(e,i);case"pulse":const i=new n(e);return(t,e)=>i.update(t,e);case"float":const o=new r(e);return(t,e)=>o.update(t,e);default:return(t,e)=>{}}}start(){this.isRunning||(this.isRunning=!0,this.lastTime=performance.now(),this.animate())}stop(){this.isRunning=!1,this.animationFrame&&(cancelAnimationFrame(this.animationFrame),this.animationFrame=null)}animate(){if(!this.isRunning)return;const t=performance.now(),e=t-this.lastTime;this.lastTime=t,this.animations.forEach((t,i)=>{const s=document.querySelector(`[data-item-id="${i}"]`);s&&t(s,e)}),this.animationFrame=requestAnimationFrame(()=>this.animate())}getAnimationCount(){return this.animations.size}clear(){this.animations.clear()}}class s{constructor(t={}){e(this,"speed"),e(this,"intensity"),e(this,"phase"),this.speed=t.speed||2e3,this.intensity=t.intensity||.05,this.phase=t.phase||0}update(t,e){const i=1+Math.sin(this.phase)*this.intensity;t.style.transform=`scale(${i})`,this.phase+=e/this.speed*Math.PI*2}}class n{constructor(t={}){e(this,"speed"),e(this,"phase"),this.speed=t.speed||1500,this.phase=t.phase||0}update(t,e){const i=.8+.2*Math.sin(this.phase);t.style.opacity=i.toString(),this.phase+=e/this.speed*Math.PI*2}}class r{constructor(t={}){e(this,"speed"),e(this,"intensity"),e(this,"phase"),this.speed=t.speed||3e3,this.intensity=t.intensity||5,this.phase=t.phase||0}update(t,e){const i=Math.sin(this.phase)*this.intensity;t.style.transform=`translateY(${i}px)`,this.phase+=e/this.speed*Math.PI*2}}class o{static parseSize(t){const[e,i]=t.split("x").map(Number);return{cols:e||1,rows:i||1}}static calculateDimensions(t,e,i,s){const{cols:n,rows:r}=this.parseSize(t),o=(e-i*(s-1))/s;return{width:o*n+i*(n-1),height:o*r}}static morphSize(t,e,i){const s=this.parseSize(t),n=this.parseSize(e),r=s.cols+(n.cols-s.cols)*i,o=s.rows+(n.rows-s.rows)*i;return`${Math.round(r)}x${Math.round(o)}`}static isValidSize(t){return/^\d+x\d+$/.test(t)}static getMinSize(t){const{cols:e,rows:i}=this.parseSize(t);return`${Math.max(1,Math.floor(e/2))}x${Math.max(1,Math.floor(i/2))}`}static getMaxSize(t){const{cols:e,rows:i}=this.parseSize(t);return`${2*e}x${2*i}`}static calculateGridPosition(t,e,i){const s=i.indexOf(Math.min(...i));return{x:s,y:i[s]}}static updateColumnHeights(t,e,i,s){const n=[...t];return n[e]+=i+s,n}}class a{constructor(){e(this,"morphingItems",new Map),e(this,"originalSizes",new Map),e(this,"morphAnimationFrame",null),e(this,"isRunning",!1),this.start()}morphItem(t,e,i=500){const s=document.querySelector(`[data-item-id="${t}"]`);if(!s)return;const n=s.getAttribute("data-size")||"1x1",r={originalSize:this.originalSizes.get(t)||n,targetSize:e,duration:i,startTime:Date.now(),progress:0,update:function(t){return this.progress=Math.min((t-this.startTime)/this.duration,1),this.progress},isComplete:function(){return this.progress>=1}};this.morphingItems.set(t,r),s.classList.add("morphing"),this.dispatchEvent("item:morph",{itemId:t,targetSize:e,duration:i})}resetItem(t,e=500){const i=this.originalSizes.get(t);i&&this.morphItem(t,i,e)}isMorphing(t){return this.morphingItems.has(t)}storeOriginalSize(t,e){this.originalSizes.set(t,e)}start(){this.isRunning||(this.isRunning=!0,this.animate())}stop(){this.isRunning=!1,this.morphAnimationFrame&&(cancelAnimationFrame(this.morphAnimationFrame),this.morphAnimationFrame=null)}animate(){if(!this.isRunning)return;const t=Date.now(),e=[];this.morphingItems.forEach((i,s)=>{const n=document.querySelector(`[data-item-id="${s}"]`);if(!n)return void e.push(s);const r=i.update(t),a=o.morphSize(i.originalSize,i.targetSize,r);n.setAttribute("data-size",a),n.style.gridColumn=`span ${o.parseSize(a).cols}`,n.style.gridRow=`span ${o.parseSize(a).rows}`,i.isComplete()&&(e.push(s),n.classList.remove("morphing"),this.dispatchEvent("item:morph-end",{itemId:s,finalSize:i.targetSize}))}),e.forEach(t=>{this.morphingItems.delete(t)}),this.morphAnimationFrame=requestAnimationFrame(()=>this.animate())}dispatchEvent(t,e){const i=new CustomEvent(t,{detail:e,bubbles:!0,cancelable:!0});document.dispatchEvent(i)}getMorphingCount(){return this.morphingItems.size}clear(){this.morphingItems.clear()}getMorphingState(t){return this.morphingItems.get(t)}}class h{constructor(t={}){e(this,"longPressTimers",new Map),e(this,"touchStartPositions",new Map),e(this,"longPressDelay",500),e(this,"touchEnabled",!0),this.longPressDelay=t.longPressDelay||500,this.touchEnabled=!1!==t.touchEnabled}setupItemListeners(t,e){!1!==t.hoverable&&(e.addEventListener("mouseenter",e=>this.handleMouseEnter(e,t)),e.addEventListener("mouseleave",e=>this.handleMouseLeave(e,t))),!1!==t.clickable&&e.addEventListener("click",e=>this.handleClick(e,t)),this.touchEnabled&&(e.addEventListener("touchstart",e=>this.handleTouchStart(e,t)),e.addEventListener("touchend",e=>this.handleTouchEnd(e,t)),e.addEventListener("touchmove",e=>this.handleTouchMove(e,t))),e.addEventListener("keydown",e=>this.handleKeyDown(e,t)),e.setAttribute("tabindex","0")}handleMouseEnter(t,e){const i=t.target;i.classList.add("hover"),e.morphOnHover&&e.morphSize&&this.dispatchMorphEvent(i,e.morphSize,e.morphDuration||300),e.onHover&&e.onHover(t,e),this.dispatchEvent("item:hover",{item:e,element:i})}handleMouseLeave(t,e){const i=t.target;if(i.classList.remove("hover"),e.morphOnHover&&e.morphSize){const t=i.getAttribute("data-original-size")||e.size;this.dispatchMorphEvent(i,t,e.morphDuration||300)}this.dispatchEvent("item:hover-end",{item:e,element:i})}handleClick(t,e){e.onClick&&e.onClick(t,e),this.dispatchEvent("item:click",{item:e,event:t})}handleTouchStart(t,e){const i=t.touches[0],s=t.target.getAttribute("data-item-id");if(s){this.touchStartPositions.set(s,{x:i.clientX,y:i.clientY});const n=window.setTimeout(()=>{this.handleLongPress(t,e)},this.longPressDelay);this.longPressTimers.set(s,n)}}handleTouchEnd(t,e){const i=t.target.getAttribute("data-item-id");if(i){const t=this.longPressTimers.get(i);t&&(clearTimeout(t),this.longPressTimers.delete(i)),this.touchStartPositions.delete(i)}}handleTouchMove(t,e){const i=t.target.getAttribute("data-item-id");if(i){const e=this.touchStartPositions.get(i);if(e){const s=t.touches[0],n=Math.abs(s.clientX-e.x),r=Math.abs(s.clientY-e.y);if(n>10||r>10){const t=this.longPressTimers.get(i);t&&(clearTimeout(t),this.longPressTimers.delete(i))}}}}handleLongPress(t,e){const i=t.target;e.morphOnLongPress&&e.morphSize&&this.dispatchMorphEvent(i,e.morphSize,e.morphDuration||500),e.onLongPress&&e.onLongPress(t,e),this.dispatchEvent("item:longpress",{item:e,element:i})}handleKeyDown(t,e){const i=t.target;switch(t.key){case"Enter":case" ":t.preventDefault(),this.handleClick(t,e);break;case"Escape":if(e.morphOnHover&&e.morphSize){const t=i.getAttribute("data-original-size")||e.size;this.dispatchMorphEvent(i,t,e.morphDuration||300)}}}dispatchMorphEvent(t,e,i){const s=new CustomEvent("item:morph-request",{detail:{element:t,targetSize:e,duration:i},bubbles:!0,cancelable:!0});t.dispatchEvent(s)}dispatchEvent(t,e){const i=new CustomEvent(t,{detail:e,bubbles:!0,cancelable:!0});document.dispatchEvent(i)}cleanupItemListeners(t){const e=t.getAttribute("data-item-id");if(e){const t=this.longPressTimers.get(e);t&&(clearTimeout(t),this.longPressTimers.delete(e)),this.touchStartPositions.delete(e)}}getActiveTimersCount(){return this.longPressTimers.size}clearAllTimers(){this.longPressTimers.forEach(t=>clearTimeout(t)),this.longPressTimers.clear(),this.touchStartPositions.clear()}}class c extends HTMLElement{constructor(){super(),e(this,"config"),e(this,"items",new Map),e(this,"animationEngine"),e(this,"morphingManager"),e(this,"interactionHandler"),e(this,"layout",null),e(this,"resizeObserver",null),e(this,"intersectionObserver",null),e(this,"container",null),this.config=this.getDefaultConfig(),this.animationEngine=new i,this.morphingManager=new a,this.interactionHandler=new h({longPressDelay:this.config.longPressDelay,touchEnabled:this.config.touchEnabled})}getDefaultConfig(){return{columns:6,gap:20,padding:20,animation:!0,animationDuration:300,breathingEffect:!0,breathingSpeed:2e3,morphing:!0,morphDuration:500,hoverMorph:!0,longPressMorph:!0,responsive:!0,breakpoints:{mobile:{columns:2,gap:12},tablet:{columns:4,gap:16},desktop:{columns:6,gap:20}},longPressDelay:500,touchEnabled:!0,theme:"default",borderRadius:12,borderWidth:0,shadow:!0,throttleResize:100,useTransform:!0,hardwareAcceleration:!0}}readAttributes(){const t=this.getAttribute("columns");t&&(this.config.columns=parseInt(t));const e=this.getAttribute("gap");e&&(this.config.gap=parseInt(e));const i=this.getAttribute("padding");i&&(this.config.padding=parseInt(i));const s=this.getAttribute("border-width");s&&(this.config.borderWidth=parseInt(s));const n=this.getAttribute("border-radius");n&&(this.config.borderRadius=parseInt(n))}connectedCallback(){this.readAttributes(),this.setupComponent(),this.setupEventListeners(),this.setupObservers(),this.applyTheme(),this.refresh()}disconnectedCallback(){this.cleanup()}static get observedAttributes(){return["columns","gap","padding","border-width","border-radius"]}attributeChangedCallback(t,e,i){if(e!==i){switch(t){case"columns":this.config.columns=i?parseInt(i):4;break;case"gap":this.config.gap=i?parseInt(i):16;break;case"padding":this.config.padding=i?parseInt(i):20;break;case"border-width":this.config.borderWidth=i?parseInt(i):0;break;case"border-radius":this.config.borderRadius=i?parseInt(i):8}this.setupComponent(),this.refresh()}}setupComponent(){this.container=document.createElement("div"),this.container.className="masonry-container",this.appendChild(this.container),this.style.setProperty("--columns",this.config.columns?.toString()||"4"),this.style.setProperty("--gap",`${this.config.gap}px`),this.style.setProperty("--padding",`${this.config.padding}px`),this.style.setProperty("--border-radius",`${this.config.borderRadius}px`),this.style.setProperty("--border-width",`${this.config.borderWidth}px`),this.style.setProperty("--transition",`${this.config.animationDuration}ms ease`),this.style.setProperty("--breathing-speed",`${this.config.breathingSpeed}ms`),this.style.setProperty("--morph-duration",`${this.config.morphDuration}ms`)}setupEventListeners(){this.addEventListener("item:morph-request",t=>{const e=t,{element:i,targetSize:s,duration:n}=e.detail,r=i.getAttribute("data-item-id");r&&this.morphingManager.morphItem(r,s,n)});const t=this.throttle(()=>{this.refresh()},this.config.throttleResize||100);window.addEventListener("resize",t)}setupObservers(){this.resizeObserver=new ResizeObserver(()=>{this.refresh()}),this.resizeObserver.observe(this),this.intersectionObserver=new IntersectionObserver(t=>{t.forEach(t=>{t.isIntersecting?t.target.classList.add("visible"):t.target.classList.remove("visible")})},{threshold:.1})}applyTheme(){const t={default:{"--bg-color":"#ffffff","--text-color":"#000000","--border-color":"#333333","--shadow":"0 2px 8px rgba(0, 0, 0, 0.1)","--hover-shadow":"0 4px 16px rgba(0, 0, 0, 0.15)","--hover-scale":"1.02"},dark:{"--bg-color":"#1a1a1a","--text-color":"#ffffff","--border-color":"#333333","--shadow":"0 2px 8px rgba(0, 0, 0, 0.3)","--hover-shadow":"0 4px 16px rgba(0, 0, 0, 0.4)","--hover-scale":"1.02"},minimal:{"--bg-color":"#fafafa","--text-color":"#333333","--border-color":"#e5e5e5","--shadow":"none","--hover-shadow":"0 2px 8px rgba(0, 0, 0, 0.1)","--hover-scale":"1.01"},glass:{"--bg-color":"rgba(255, 255, 255, 0.1)","--text-color":"#ffffff","--border-color":"rgba(255, 255, 255, 0.2)","--shadow":"0 8px 32px rgba(31, 38, 135, 0.37)","--hover-shadow":"0 12px 40px rgba(31, 38, 135, 0.5)","--hover-scale":"1.05"},neon:{"--bg-color":"#000000","--text-color":"#00ff00","--border-color":"#00ff00","--shadow":"0 0 10px rgba(0, 255, 0, 0.5)","--hover-shadow":"0 0 20px rgba(0, 255, 0, 0.8)","--hover-scale":"1.1"},custom:{"--bg-color":"#ffffff","--text-color":"#000000","--border-color":"#e0e0e0","--shadow":"0 2px 8px rgba(0, 0, 0, 0.1)","--hover-shadow":"0 4px 16px rgba(0, 0, 0, 0.15)","--hover-scale":"1.02"}},e=t[this.config.theme||"default"]||t.default;Object.entries(e).forEach(([t,e])=>{this.style.setProperty(t,e)})}addItem(t){this.items.set(t.id,t),this.createItemElement(t),this.refresh(),this.dispatchCustomEvent("item:added",{item:t})}removeItem(t){const e=this.items.get(t);if(e){this.items.delete(t);const i=this.querySelector(`[data-item-id="${t}"]`);i&&(this.interactionHandler.cleanupItemListeners(i),i.remove()),this.refresh(),this.dispatchCustomEvent("item:removed",{item:e})}}updateItem(t,e){const i=this.items.get(t);if(i){const s={...i,...e};this.items.set(t,s),this.updateItemElement(t,s),this.refresh(),this.dispatchCustomEvent("item:updated",{item:s})}}createItemElement(t){if(!this.container)return;const e=document.createElement("div");e.className="masonry-item",e.setAttribute("data-item-id",t.id),e.setAttribute("data-size",t.size),e.setAttribute("data-original-size",t.size),t.backgroundColor&&(e.style.backgroundColor=t.backgroundColor),t.textColor&&(e.style.color=t.textColor),t.image&&(e.style.backgroundImage=`url(${t.image})`,e.style.backgroundSize="cover",e.style.backgroundPosition="center"),t.content&&(e.innerHTML=t.content),this.interactionHandler.setupItemListeners(t,e),this.config.animation&&t.animationType&&"none"!==t.animationType&&this.animationEngine.addAnimation(t.id,t.animationType,{speed:this.config.breathingSpeed,intensity:.05}),this.morphingManager.storeOriginalSize(t.id,t.size),this.intersectionObserver&&this.intersectionObserver.observe(e),this.container.appendChild(e)}updateItemElement(t,e){const i=this.querySelector(`[data-item-id="${t}"]`);i&&(i.setAttribute("data-size",e.size),i.setAttribute("data-original-size",e.size),e.backgroundColor&&(i.style.backgroundColor=e.backgroundColor),e.textColor&&(i.style.color=e.textColor),e.image&&(i.style.backgroundImage=`url(${e.image})`),e.content&&(i.innerHTML=e.content),this.config.animation&&e.animationType&&"none"!==e.animationType?this.animationEngine.addAnimation(e.id,e.animationType):this.animationEngine.removeAnimation(e.id))}refresh(){if(!this.container)return;const t=this.container.offsetWidth,e=this.config.columns||4,i=this.config.gap||16,s=new Array(e).fill(0),n=Array.from(this.container.children);n.forEach(n=>{const r=n.getAttribute("data-size")||"1x1",{cols:a,rows:h}=o.parseSize(r),c=s.indexOf(Math.min(...s)),l=(t-i*(e-1))/e*h;n.style.gridColumn=`span ${a}`,n.style.gridRow=`span ${h}`,s[c]+=l+i}),this.layout={columns:e,gap:i,padding:this.config.padding||20,containerWidth:t,containerHeight:Math.max(...s),itemCount:n.length,columnHeights:s},this.dispatchCustomEvent("layout:updated",{layout:this.layout})}morphItem(t,e,i){this.morphingManager.morphItem(t,e,i||this.config.morphDuration||500)}resetItem(t){this.morphingManager.resetItem(t)}getItem(t){return this.items.get(t)}getItems(){return Array.from(this.items.values())}getLayoutInfo(){return this.layout}updateConfig(t){this.config={...this.config,...t},this.setupComponent(),this.applyTheme(),this.refresh()}throttle(t,e){let i=null;return(...s)=>{i||(i=window.setTimeout(()=>{t.apply(this,s),i=null},e))}}dispatchCustomEvent(t,e){const i=new CustomEvent(t,{detail:e,bubbles:!0,cancelable:!0});this.dispatchEvent(i)}cleanup(){this.resizeObserver&&this.resizeObserver.disconnect(),this.intersectionObserver&&this.intersectionObserver.disconnect(),this.animationEngine.stop(),this.morphingManager.stop(),this.interactionHandler.clearAllTimers()}destroy(){this.cleanup(),this.items.clear(),this.container&&(this.container.innerHTML="")}}customElements.define("hy-masonry",c);const l=Object.freeze(Object.defineProperty({__proto__:null,HyMasonry:c},Symbol.toStringTag,{value:"Module"}));"undefined"!=typeof window&&"undefined"!=typeof customElements&&Promise.resolve().then(()=>l),exports.AnimationEngine=i,exports.HyMasonry=c,exports.InteractionHandler=h,exports.MorphingManager=a,exports.SizeCalculator=o,exports.default=c;
//# sourceMappingURL=hy-masonry.cjs.js.map