UNPKG

vue-hover-effect

Version:

> WebGL-powered image hover transition effects for Vue 3, built on top of [Three.js](https://threejs.org/) and [GSAP](https://gsap.com/). Inspired by [Robin Delaporte's hover-effect](https://github.com/robin-dela/hover-effect).

77 lines (63 loc) 11 kB
(function(u,r){typeof exports=="object"&&typeof module<"u"?r(exports,require("vue"),require("three"),require("gsap")):typeof define=="function"&&define.amd?define(["exports","vue","three","gsap"],r):(u=typeof globalThis<"u"?globalThis:u||self,r(u.VueHoverEffect={},u.Vue,u.THREE,u.gsap))})(this,function(u,r,K,C){"use strict";function Q(n){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(n){for(const t in n)if(t!=="default"){const i=Object.getOwnPropertyDescriptor(n,t);Object.defineProperty(e,t,i.get?i:{enumerable:!0,get:()=>n[t]})}}return e.default=n,Object.freeze(e)}const o=Q(K),X="https://cdn.jsdelivr.net/npm/vue-hover-effect@0.0.1-stable/dist/lib-images/displacements",B=Array.from({length:16},(n,e)=>`${X}/${e+1}.webp`),U=B[0],Y=r.defineComponent({__name:"SingleHoverEffect",props:{distortionClass:{},displacementImage:{},image1:{},image2:{},imagesRatio:{},intensity:{},intensity1:{},intensity2:{},angle:{},angle1:{},angle2:{},speed:{},speedIn:{},speedOut:{},hover:{type:Boolean},easing:{},video:{type:Boolean}},setup(n){const e=n,t=r.ref(null);let i,s;r.onMounted(async()=>{if(!t.value)return;const{createSingleHoverEffect:x}=await Promise.resolve().then(()=>$);i=x({parent:t.value,image1:e.image1,image2:e.image2,displacementImage:e.displacementImage??U,imagesRatio:e.imagesRatio,intensity:e.intensity,intensity1:e.intensity1,intensity2:e.intensity2,angle:e.angle,angle1:e.angle1,angle2:e.angle2,speed:e.speed,speedIn:e.speedIn,speedOut:e.speedOut,hover:e.hover,easing:e.easing,video:e.video}),typeof ResizeObserver<"u"&&(s=new ResizeObserver(M=>{for(const w of M)w.target===t.value&&(i==null||i.resize(w.target.clientWidth,w.target.clientHeight))}),s.observe(t.value))});function y(){i==null||i.next()}function v(){i==null||i.previous()}return r.onUnmounted(()=>{i=void 0,s&&t.value&&(s.unobserve(t.value),s.disconnect())}),(x,M)=>(r.openBlock(),r.createElementBlock(r.Fragment,null,[r.createElementVNode("div",{ref_key:"container",ref:t,class:r.normalizeClass(["distortion-container",x.distortionClass])},null,2),r.renderSlot(x.$slots,"controllers",{funcs:{goNext:y,goPrev:v}})],64))}}),Z=((n,e)=>{const t=n.__vccOpts||n;for(const[i,s]of e)t[i]=s;return t})(r.defineComponent({__name:"MultipleHoverEffect",props:{distortionClass:{},displacementImage:{},images:{},imagesRatio:{},speedIn:{},speedOut:{},intensity1:{},intensity2:{},angle1:{},angle2:{},hover:{type:Boolean},easing:{}},setup(n){const e=n,t=r.ref(null);let i;r.onMounted(async()=>{if(!t.value)return;const{createMultiImageEffect:v}=await Promise.resolve().then(()=>$);i=v({parent:t.value,images:e.images,displacementImage:e.displacementImage??U,imagesRatio:e.imagesRatio,speedIn:e.speedIn,speedOut:e.speedOut,intensity1:e.intensity1,intensity2:e.intensity2,angle1:e.angle1,angle2:e.angle2,hover:e.hover,easing:e.easing})});function s(){i==null||i.next()}function y(){i==null||i.previous()}return r.onUnmounted(()=>{i=void 0}),(v,x)=>(r.openBlock(),r.createElementBlock(r.Fragment,null,[r.createElementVNode("div",{ref_key:"container",ref:t,class:r.normalizeClass(["distortion-container",v.distortionClass])},null,2),r.renderSlot(v.$slots,"controllers",{funcs:{goNext:s,goPrev:y}},void 0,!0)],64))}}),[["__scopeId","data-v-5f8717fc"]]);function q(n){console.log("%c Hover effect (Multi) by Robin Delaporte (modified) ","color: #bada55; font-size: 0.8rem");const{parent:e,displacementImage:t,images:i,imagesRatio:s=1,speedIn:y=1.2,speedOut:v=1,intensity1:x=1,intensity2:M=1,angle1:w=Math.PI/4,angle2:N=-(Math.PI/4)*3,hover:A=!1,easing:j="expo.out"}=n;if(!e){console.warn("MultiImageEffect: no parent element provided.");return}if(!t||!i||i.length<2){console.warn("MultiImageEffect: displacementImage is required and images[] must have at least 2 items.");return}const V=new o.Scene,z=new o.OrthographicCamera(e.offsetWidth/-2,e.offsetWidth/2,e.offsetHeight/2,e.offsetHeight/-2,1,1e3);z.position.z=1;const E=new o.WebGLRenderer({alpha:!0,antialias:!1});E.setPixelRatio(window.devicePixelRatio),E.setClearColor(16777215,0),E.setSize(e.offsetWidth,e.offsetHeight),e.appendChild(E.domElement);function I(){E.render(V,z)}const H=new o.TextureLoader;H.crossOrigin="";const L=H.load(t,I);L.magFilter=L.minFilter=o.LinearFilter;const l=i.map(a=>{const _=H.load(a,I);return _.magFilter=_.minFilter=o.LinearFilter,_});let g=0,D=!1;const k=` varying vec2 vUv; void main(){ vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0); } `,R=` varying vec2 vUv; uniform float dispFactor; uniform sampler2D disp; uniform sampler2D texture1; uniform sampler2D texture2; uniform float angle1; uniform float angle2; uniform float intensity1; uniform float intensity2; void main(){ vec4 dispTex = texture2D(disp, vUv); vec2 dispVec = dispTex.rg; vec2 uv = vUv; vec2 distortedPos1 = uv + mat2(cos(angle1), -sin(angle1), sin(angle1), cos(angle1)) * dispVec * intensity1 * dispFactor; vec2 distortedPos2 = uv + mat2(cos(angle2), -sin(angle2), sin(angle2), cos(angle2)) * dispVec * intensity2 * (1.0 - dispFactor); vec4 _texture1 = texture2D(texture1, distortedPos1); vec4 _texture2 = texture2D(texture2, distortedPos2); gl_FragColor = mix(_texture1, _texture2, dispFactor); } `;let P=1,d=1;function m(){e.offsetHeight/e.offsetWidth<s?(P=1,d=e.offsetHeight/e.offsetWidth/s):(P=e.offsetWidth/e.offsetHeight*s,d=1)}m();const p=new o.ShaderMaterial({uniforms:{dispFactor:{value:0},disp:{value:L},texture1:{value:l[0]},texture2:{value:l[1]},angle1:{value:w},angle2:{value:N},intensity1:{value:x},intensity2:{value:M},res:{value:new o.Vector4(e.offsetWidth,e.offsetHeight,P,d)}},vertexShader:k,fragmentShader:R,transparent:!0,opacity:1}),O=new o.PlaneGeometry(e.offsetWidth,e.offsetHeight,1),T=new o.Mesh(O,p);V.add(T),I(),window.addEventListener("resize",()=>{E.setSize(e.offsetWidth,e.offsetHeight),m(),T.geometry=new o.PlaneGeometry(e.offsetWidth,e.offsetHeight,1),I()});function F(a,_){D||a<0||a>=l.length||a!==g&&(D=!0,p.uniforms.texture2.value=l[a],C.to(p.uniforms.dispFactor,{duration:_,value:1,ease:j,onUpdate:I,onComplete:()=>{p.uniforms.texture1.value=l[a],p.uniforms.dispFactor.value=0,g=a,D=!1,I()}}))}function W(){const a=(g+1)%l.length;F(a,y)}function b(){let a=g-1;a<0&&(a=l.length-1),F(a,v)}return A&&(e.addEventListener("mouseenter",W),e.addEventListener("mouseleave",b),e.addEventListener("touchstart",W),e.addEventListener("touchend",b)),{next:W,previous:b}}function G(n){console.log("%c Hover effect (Single) by Robin Delaporte – https://github.com/robin-dela/hover-effect","color: #bada55; font-size: 0.8rem");function e(...c){return c.find(f=>f!==void 0)}const t=n.parent,i=n.displacementImage,s=n.image1,y=n.image2;if(!t){console.warn("SingleHoverEffect: no parent element provided.");return}if(!i||!s||!y){console.warn("SingleHoverEffect: displacementImage/image1/image2 missing.");return}const v=e(n.imagesRatio,1),x=e(n.intensity1,n.intensity,1),M=e(n.intensity2,n.intensity,1),w=e(n.angle,Math.PI/4),N=e(n.angle1,w),A=e(n.angle2,-w*3),j=e(n.speedIn,n.speed,1.6),V=e(n.speedOut,n.speed,1.2),z=e(n.hover,!0),E=e(n.easing,"expo.out"),I=e(n.video,!1),H=new o.Scene,L=new o.OrthographicCamera(t.offsetWidth/-2,t.offsetWidth/2,t.offsetHeight/2,t.offsetHeight/-2,1,1e3);L.position.z=1;const l=new o.WebGLRenderer({alpha:!0,antialias:!1});l.setPixelRatio(window.devicePixelRatio),l.setClearColor(16777215,0),l.setSize(t.offsetWidth,t.offsetHeight),t.appendChild(l.domElement);function g(){l.render(H,L)}const D=` varying vec2 vUv; void main(){ vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0); } `,k=` varying vec2 vUv; uniform float dispFactor; uniform sampler2D disp; uniform sampler2D texture1; uniform sampler2D texture2; uniform float angle1; uniform float angle2; uniform float intensity1; uniform float intensity2; uniform vec4 res; mat2 getRotM(float angle){ float s = sin(angle); float c = cos(angle); return mat2(c, -s, s, c); } void main(){ // displacement vec4 dispTex = texture2D(disp, vUv); vec2 dispVec = dispTex.rg; // Simple approach vec2 uv = vUv; vec2 distortedPos1 = uv + getRotM(angle1) * dispVec * intensity1 * dispFactor; vec2 distortedPos2 = uv + getRotM(angle2) * dispVec * intensity2 * (1.0 - dispFactor); vec4 _texture1 = texture2D(texture1, distortedPos1); vec4 _texture2 = texture2D(texture2, distortedPos2); gl_FragColor = mix(_texture1, _texture2, dispFactor); } `,R=new o.TextureLoader;R.crossOrigin="";const P=R.load(i,g);P.magFilter=P.minFilter=o.LinearFilter;let d,m;if(I){const c=document.createElement("video");c.autoplay=!0,c.loop=!0,c.muted=!0,c.src=s,c.load();const f=document.createElement("video");f.autoplay=!0,f.loop=!0,f.muted=!0,f.src=y,f.load(),d=new o.VideoTexture(c),m=new o.VideoTexture(f),d.magFilter=d.minFilter=o.LinearFilter,m.magFilter=m.minFilter=o.LinearFilter,c.addEventListener("loadeddata",()=>{c.play(),d=new o.VideoTexture(c),d.magFilter=d.minFilter=o.LinearFilter,F.uniforms.texture1.value=d}),f.addEventListener("loadeddata",()=>{f.play(),m=new o.VideoTexture(f),m.magFilter=m.minFilter=o.LinearFilter,F.uniforms.texture2.value=m}),requestAnimationFrame(function h(){requestAnimationFrame(h),l.render(H,L)})}else d=R.load(s,g),m=R.load(y,g),d.magFilter=d.minFilter=o.LinearFilter,m.magFilter=m.minFilter=o.LinearFilter;let p=1,O=1;function T(c,f){const h=c||t.offsetWidth,S=f||t.offsetHeight;S/h<v?(p=1,O=S/h/v):(p=h/S*v,O=1)}T();const F=new o.ShaderMaterial({uniforms:{dispFactor:{value:0},disp:{value:P},texture1:{value:d},texture2:{value:m},angle1:{value:N},angle2:{value:A},intensity1:{value:x},intensity2:{value:M},res:{value:new o.Vector4(t.offsetWidth,t.offsetHeight,p,O)}},vertexShader:D,fragmentShader:k,transparent:!0,opacity:1}),W=new o.PlaneGeometry(t.offsetWidth,t.offsetHeight,1),b=new o.Mesh(W,F);H.add(b);function a(){C.to(F.uniforms.dispFactor,{duration:j,value:1,ease:E,onUpdate:g,onComplete:g})}function _(){C.to(F.uniforms.dispFactor,{duration:V,value:0,ease:E,onUpdate:g,onComplete:g})}z&&(t.addEventListener("mouseenter",a),t.addEventListener("mouseleave",_),t.addEventListener("touchstart",a),t.addEventListener("touchend",_));function J(c,f){const h=c||t.offsetWidth,S=f||t.offsetHeight;l.setSize(h,S),T(h,S),F.uniforms.res.value.set(h,S,p,O),b.geometry=new o.PlaneGeometry(h,S,1),g()}return window.addEventListener("resize",()=>J()),{next:a,previous:_,resize:J}}const $=Object.freeze(Object.defineProperty({__proto__:null,createMultiImageEffect:q,createSingleHoverEffect:G},Symbol.toStringTag,{value:"Module"}));u.DEFAULT_DISPLACEMENT=U,u.DISPLACEMENT_URLS=B,u.MultipleHoverEffect=Z,u.SingleHoverEffect=Y,u.createMultiImageEffect=q,u.createSingleHoverEffect=G,Object.defineProperty(u,Symbol.toStringTag,{value:"Module"})});