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
JavaScript
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
};