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).

452 lines (438 loc) 13.3 kB
import { defineComponent as B, ref as G, onMounted as $, onUnmounted as k, openBlock as j, createElementBlock as q, Fragment as J, createElementVNode as K, normalizeClass as Q, renderSlot as X } from "vue"; import * as n from "three"; import A from "gsap"; const Z = "0.0.1-stable", ee = `https://cdn.jsdelivr.net/npm/vue-hover-effect@${Z}/dist/lib-images/displacements`, te = Array.from( { length: 16 }, (i, e) => `${ee}/${e + 1}.webp` ), Y = te[0], se = /* @__PURE__ */ B({ __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(i) { const e = i, t = G(null); let o, s; $(async () => { if (!t.value) return; const { createSingleHoverEffect: h } = await import("./index-1eHrsGM9.js"); o = h({ parent: t.value, image1: e.image1, image2: e.image2, displacementImage: e.displacementImage ?? Y, 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((R) => { for (const E of R) E.target === t.value && (o == null || o.resize(E.target.clientWidth, E.target.clientHeight)); }), s.observe(t.value)); }); function p() { o == null || o.next(); } function m() { o == null || o.previous(); } return k(() => { o = void 0, s && t.value && (s.unobserve(t.value), s.disconnect()); }), (h, R) => (j(), q(J, null, [ K("div", { ref_key: "container", ref: t, class: Q(["distortion-container", h.distortionClass]) }, null, 2), X(h.$slots, "controllers", { funcs: { goNext: p, goPrev: m } }) ], 64)); } }), ne = /* @__PURE__ */ B({ __name: "MultipleHoverEffect", props: { distortionClass: {}, displacementImage: {}, images: {}, imagesRatio: {}, speedIn: {}, speedOut: {}, intensity1: {}, intensity2: {}, angle1: {}, angle2: {}, hover: { type: Boolean }, easing: {} }, setup(i) { const e = i, t = G(null); let o; $(async () => { if (!t.value) return; const { createMultiImageEffect: m } = await import("./index-1eHrsGM9.js"); o = m({ parent: t.value, images: e.images, displacementImage: e.displacementImage ?? Y, 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() { o == null || o.next(); } function p() { o == null || o.previous(); } return k(() => { o = void 0; }), (m, h) => (j(), q(J, null, [ K("div", { ref_key: "container", ref: t, class: Q(["distortion-container", m.distortionClass]) }, null, 2), X(m.$slots, "controllers", { funcs: { goNext: s, goPrev: p } }, void 0, !0) ], 64)); } }), ie = (i, e) => { const t = i.__vccOpts || i; for (const [o, s] of e) t[o] = s; return t; }, re = /* @__PURE__ */ ie(ne, [["__scopeId", "data-v-5f8717fc"]]); function le(i) { console.log( "%c Hover effect (Multi) by Robin Delaporte (modified) ", "color: #bada55; font-size: 0.8rem" ); const { parent: e, displacementImage: t, images: o, imagesRatio: s = 1, speedIn: p = 1.2, speedOut: m = 1, intensity1: h = 1, intensity2: R = 1, angle1: E = Math.PI / 4, angle2: T = -(Math.PI / 4) * 3, hover: V = !1, easing: U = "expo.out" } = i; if (!e) { console.warn("MultiImageEffect: no parent element provided."); return; } if (!t || !o || o.length < 2) { console.warn( "MultiImageEffect: displacementImage is required and images[] must have at least 2 items." ); return; } const b = new n.Scene(), z = new n.OrthographicCamera( e.offsetWidth / -2, e.offsetWidth / 2, e.offsetHeight / 2, e.offsetHeight / -2, 1, 1e3 ); z.position.z = 1; const y = new n.WebGLRenderer({ alpha: !0, antialias: !1 }); y.setPixelRatio(window.devicePixelRatio), y.setClearColor(16777215, 0), y.setSize(e.offsetWidth, e.offsetHeight), e.appendChild(y.domElement); function w() { y.render(b, z); } const _ = new n.TextureLoader(); _.crossOrigin = ""; const H = _.load(t, w); H.magFilter = H.minFilter = n.LinearFilter; const r = o.map((a) => { const F = _.load(a, w); return F.magFilter = F.minFilter = n.LinearFilter, F; }); let f = 0, W = !1; const C = ` varying vec2 vUv; void main(){ vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0); } `, S = ` 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 L = 1, l = 1; function u() { e.offsetHeight / e.offsetWidth < s ? (L = 1, l = e.offsetHeight / e.offsetWidth / s) : (L = e.offsetWidth / e.offsetHeight * s, l = 1); } u(); const g = new n.ShaderMaterial({ uniforms: { dispFactor: { value: 0 }, disp: { value: H }, texture1: { value: r[0] }, // start with the first image texture2: { value: r[1] }, // second image angle1: { value: E }, angle2: { value: T }, intensity1: { value: h }, intensity2: { value: R }, res: { value: new n.Vector4( e.offsetWidth, e.offsetHeight, L, l ) } }, vertexShader: C, fragmentShader: S, transparent: !0, opacity: 1 }), P = new n.PlaneGeometry(e.offsetWidth, e.offsetHeight, 1), D = new n.Mesh(P, g); b.add(D), w(), window.addEventListener("resize", () => { y.setSize(e.offsetWidth, e.offsetHeight), u(), D.geometry = new n.PlaneGeometry(e.offsetWidth, e.offsetHeight, 1), w(); }); function x(a, F) { W || a < 0 || a >= r.length || a !== f && (W = !0, g.uniforms.texture2.value = r[a], A.to(g.uniforms.dispFactor, { duration: F, value: 1, ease: U, onUpdate: w, onComplete: () => { g.uniforms.texture1.value = r[a], g.uniforms.dispFactor.value = 0, f = a, W = !1, w(); } })); } function O() { const a = (f + 1) % r.length; x(a, p); } function M() { let a = f - 1; a < 0 && (a = r.length - 1), x(a, m); } return V && (e.addEventListener("mouseenter", O), e.addEventListener("mouseleave", M), e.addEventListener("touchstart", O), e.addEventListener("touchend", M)), { next: O, previous: M }; } function de(i) { console.log( "%c Hover effect (Single) by Robin Delaporte – https://github.com/robin-dela/hover-effect", "color: #bada55; font-size: 0.8rem" ); function e(...d) { return d.find((c) => c !== void 0); } const t = i.parent, o = i.displacementImage, s = i.image1, p = i.image2; if (!t) { console.warn("SingleHoverEffect: no parent element provided."); return; } if (!o || !s || !p) { console.warn("SingleHoverEffect: displacementImage/image1/image2 missing."); return; } const m = e(i.imagesRatio, 1), h = e(i.intensity1, i.intensity, 1), R = e(i.intensity2, i.intensity, 1), E = e(i.angle, Math.PI / 4), T = e(i.angle1, E), V = e(i.angle2, -E * 3), U = e(i.speedIn, i.speed, 1.6), b = e(i.speedOut, i.speed, 1.2), z = e(i.hover, !0), y = e(i.easing, "expo.out"), w = e(i.video, !1), _ = new n.Scene(), H = new n.OrthographicCamera( t.offsetWidth / -2, t.offsetWidth / 2, t.offsetHeight / 2, t.offsetHeight / -2, 1, 1e3 ); H.position.z = 1; const r = new n.WebGLRenderer({ alpha: !0, antialias: !1 }); r.setPixelRatio(window.devicePixelRatio), r.setClearColor(16777215, 0), r.setSize(t.offsetWidth, t.offsetHeight), t.appendChild(r.domElement); function f() { r.render(_, H); } const W = ` varying vec2 vUv; void main(){ vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0); } `, C = ` 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); } `, S = new n.TextureLoader(); S.crossOrigin = ""; const L = S.load(o, f); L.magFilter = L.minFilter = n.LinearFilter; let l, u; if (w) { const d = document.createElement("video"); d.autoplay = !0, d.loop = !0, d.muted = !0, d.src = s, d.load(); const c = document.createElement("video"); c.autoplay = !0, c.loop = !0, c.muted = !0, c.src = p, c.load(), l = new n.VideoTexture(d), u = new n.VideoTexture(c), l.magFilter = l.minFilter = n.LinearFilter, u.magFilter = u.minFilter = n.LinearFilter, d.addEventListener("loadeddata", () => { d.play(), l = new n.VideoTexture(d), l.magFilter = l.minFilter = n.LinearFilter, x.uniforms.texture1.value = l; }), c.addEventListener("loadeddata", () => { c.play(), u = new n.VideoTexture(c), u.magFilter = u.minFilter = n.LinearFilter, x.uniforms.texture2.value = u; }), requestAnimationFrame(function v() { requestAnimationFrame(v), r.render(_, H); }); } else l = S.load(s, f), u = S.load(p, f), l.magFilter = l.minFilter = n.LinearFilter, u.magFilter = u.minFilter = n.LinearFilter; let g = 1, P = 1; function D(d, c) { const v = d || t.offsetWidth, I = c || t.offsetHeight; I / v < m ? (g = 1, P = I / v / m) : (g = v / I * m, P = 1); } D(); const x = new n.ShaderMaterial({ uniforms: { dispFactor: { value: 0 }, disp: { value: L }, texture1: { value: l }, texture2: { value: u }, angle1: { value: T }, angle2: { value: V }, intensity1: { value: h }, intensity2: { value: R }, res: { value: new n.Vector4( t.offsetWidth, t.offsetHeight, g, P ) } }, vertexShader: W, fragmentShader: C, transparent: !0, opacity: 1 }), O = new n.PlaneGeometry(t.offsetWidth, t.offsetHeight, 1), M = new n.Mesh(O, x); _.add(M); function a() { A.to(x.uniforms.dispFactor, { duration: U, value: 1, ease: y, onUpdate: f, onComplete: f }); } function F() { A.to(x.uniforms.dispFactor, { duration: b, value: 0, ease: y, onUpdate: f, onComplete: f }); } z && (t.addEventListener("mouseenter", a), t.addEventListener("mouseleave", F), t.addEventListener("touchstart", a), t.addEventListener("touchend", F)); function N(d, c) { const v = d || t.offsetWidth, I = c || t.offsetHeight; r.setSize(v, I), D(v, I), x.uniforms.res.value.set( v, I, g, P ), M.geometry = new n.PlaneGeometry( v, I, 1 ), f(); } return window.addEventListener("resize", () => N()), { next: a, // image1 → image2 previous: F, // image2 → image1 resize: N }; } export { Y as DEFAULT_DISPLACEMENT, te as DISPLACEMENT_URLS, re as MultipleHoverEffect, se as SingleHoverEffect, le as createMultiImageEffect, de as createSingleHoverEffect };