为了让动画更灵活并且简单 借助gsap
让其具有更多可能,在未来更容易扩充其他动效
gsap Dom跟随鼠标移动 gsap.quickTo()
首先要监听鼠标移动,并且将移动的值转换到 -1 和 1 之间 方便处理
private mousemove(e: MouseEvent) {
const x = (e.clientX / innerWidth);
const y = (e.clientY / innerHeight);
}
上面将 位置 / 屏幕宽高
将值缩放在 0 和 1
之间
然后通过 乘2减1
将其限制在-1 和 1
之间
private mousemove(e: MouseEvent) {
const x = (e.clientX / innerWidth) * 2 - 1;
const y = (e.clientY / innerHeight) * 2 - 1;
}
在three中y轴 上面是1下面是-1 而我们窗口上面是-1 下面是1 所以取y轴剩余的高度让两者行为统一,也就是实现向着鼠标方向
private mousemove(e: MouseEvent) {
const x = (e.clientX / innerWidth) * 2 - 1) ;
const y = ((innerHeight - e.clientY) / innerHeight) * 2 - 1;
}
如此 我们获取了鼠标移动的增量,将这一向量加在camera的x和y轴即可,这里不能使用+=
这样会越来越偏 所以保存相机的原始位置。那么当前鼠标所在相机的位置,应当是原始位置加上鼠标移动的增量
这里就可以使用gsap来控制position变化
private xQuickTo = gsap.quickTo(this.camera.position, "x", {
duration: 0.5,
});
上述代码 this.camera.position
的 .x
经过0.5秒后变化到传入值,如:this.xQuickTo(this.cameraPosition.x + x)
class Shake {
cameraPosition = this.camera.position.clone();
private xQuickTo = gsap.quickTo(this.camera.position, "x", {
duration: 0.5,
});
private yQuickTo = gsap.quickTo(this.camera.position, "y", {
duration: 0.5,
});
private mousemove(e: MouseEvent) {
const x = ((e.clientX / innerWidth) * 2 - 1) / this.amplitude;
const y =
(((innerHeight - e.clientY) / innerHeight) * 2 - 1) /
this.amplitude;
this.xQuickTo(this.cameraPosition.x + x).play();
this.yQuickTo(this.cameraPosition.y + y).play();
}
}
如此 核心逻辑便完成,丰富事件监听并且加入振幅,控制相机移动范围 后完成这个class
export class Shake {
/** 振幅 鼠标晃动的影响 */
amplitude = 1;
cameraPosition = this.camera.position.clone();
private xQuickTo = gsap.quickTo(this.camera.position, "x", {
duration: 0.5,
});
private yQuickTo = gsap.quickTo(this.camera.position, "y", {
duration: 0.5,
});
constructor(public camera: THREE.Camera, public domElement: HTMLElement) {
this.domElement.addEventListener("mousemove", this.selfMouseMove);
}
private mousemove(e: MouseEvent) {
const x = ((e.clientX / innerWidth) * 2 - 1) / this.amplitude;
const y =
(((innerHeight - e.clientY) / innerHeight) * 2 - 1) /
this.amplitude;
this.xQuickTo(this.cameraPosition.x + x)
this.yQuickTo(this.cameraPosition.y + y)
}
private selfMouseMove = (e: MouseEvent) => this.mousemove.call(this, e);
destroyMouseMove() {
this.domElement.removeEventListener("mousemove", this.selfMouseMove);
}
}
接下来可以扩充一下功能, 加入鼠标按下时可以拖拽旋转,鼠标松开后回到相机在当前鼠标位置位置时的位置
为了支持这些功能 需要给shake增加暂停动画的能力 以及mousemove时记录当前鼠标对相机的位置影响,松开鼠标,相机回去时直接回到mousemove中的位置
export class Shake {
pause = false;
/** 振幅 鼠标晃动的影响 */
amplitude = 1;
cameraPosition = this.camera.position.clone();
xQuickTo = gsap.quickTo(this.camera.position, "x", {
duration: 0.5,
});
yQuickTo = gsap.quickTo(this.camera.position, "y", {
duration: 0.5,
});
private yQuickToTween: gsap.core.Tween | undefined;
private xQuickToTween: gsap.core.Tween | undefined;
point = new Vector2();
constructor(public camera: THREE.Camera, public domElement: HTMLElement) {
this.domElement.addEventListener("mousemove", this.selfMouseMove);
}
private mousemove(e: MouseEvent) {
const x = ((e.clientX / innerWidth) * 2 - 1) / this.amplitude;
const y =
(((innerHeight - e.clientY) / innerHeight) * 2 - 1) /
this.amplitude;
this.point.set(x, y);
if (this.pause) {
this.xQuickToTween && this.xQuickToTween.pause();
this.yQuickToTween && this.yQuickToTween.pause();
}else {
this.xQuickToTween = this.xQuickTo(this.cameraPosition.x + x);
this.yQuickToTween = this.yQuickTo(this.cameraPosition.y + y);
}
}
}
export class CameraShake extends Shake {
constructor(...params: ConstructorParameters<typeof Shake>) {
super(...params);
this.domElement.addEventListener("mousedown", this.selfMouseDown);
}
mousedown() {
if (this.pause) return;
this.pause = true;
document.addEventListener("mouseup", this.selfMouseUp, { once: true });
}
mouseup() {
const { x, y, z } = this.cameraPosition;
gsap.to(this.camera.position, {
x: x + this.point.x,
y: y + this.point.y,
z,
duration: 0.8,
onComplete: () => {
this.pause = false;
},
});
}
selfMouseDown = () => this.mousedown.call(this);
selfMouseUp = () => this.mouseup.call(this);
destroyMouseEvent() {
this.destroyMouseMove();
this.domElement.removeEventListener("mousedown", this.selfMouseDown);
}
}
截至目前还有一个问题 ,鼠标松开后第一次移动鼠标 动画会闪一下,他的运动路线实际是鼠标按下的位置到当前鼠标移动的位置,因为mousemove中的quickTo被暂停了 mousemove开始动画是从上次播放的位置开始计算的,因此我们需要在mouseup后的mousemove传入quickTo第二哥参数 start 也就是从哪开始计算,这是松开鼠标后第一次 mousemove ,其他时候就可以穿入undefined让其自行计算。
mouseMove(e: MouseEvent) {
// -1 ~ 1
const x = (e.clientX / innerWidth) * 2 - 1;
const y = ((innerHeight - e.clientY) / innerHeight) * 2 - 1;
if (this.pause) {
this.xQuickToTween && this.xQuickToTween.pause();
this.yQuickToTween && this.yQuickToTween.pause();
this.point.set(x, y);
} else {
this.xQuickToTween = this.xQuickTo(
this.cameraPosition.x + x,
Number.isNaN(this.point.x)
? undefined
: this.cameraPosition.x + this.point.x
);
this.yQuickToTween = this.yQuickTo(
this.cameraPosition.y + y,
Number.isNaN(this.point.y)
? undefined
: this.cameraPosition.y + this.point.y
);
this.point.set(NaN, NaN);
}
}
完整代码:文章来源:https://www.toymoban.com/news/detail-545941.html
import { Vector2, Vector3 } from "three";
import { gsap } from "gsap";
export class Shake {
pause = false;
/** 振幅 鼠标晃动的影响 */
amplitude = 1;
cameraPosition = this.camera.position.clone();
xQuickTo = gsap.quickTo(this.camera.position, "x", {
duration: 0.5,
// ease: "bounce",
});
yQuickTo = gsap.quickTo(this.camera.position, "y", {
duration: 0.5,
});
xQuickToTween?: gsap.core.Tween;
yQuickToTween?: gsap.core.Tween;
point = new Vector2();
constructor(public camera: THREE.Camera, public dom: HTMLElement) {
dom.addEventListener("mousemove", this.selfMouseMove);
}
selfMouseMove = (e: MouseEvent) => this.mouseMove.call(this, e);
destroyMouseMove = () => {
this.dom.addEventListener("mousemove", this.selfMouseMove);
};
mouseMove(e: MouseEvent) {
// -1 ~ 1
const x = ((e.clientX / innerWidth) * 2 - 1) * this.amplitude;
const y = (((innerHeight - e.clientY) / innerHeight) * 2 - 1) * this.amplitude;
if (this.pause) {
this.xQuickToTween && this.xQuickToTween.pause();
this.yQuickToTween && this.yQuickToTween.pause();
this.point.set(x, y);
} else {
this.xQuickToTween = this.xQuickTo(
this.cameraPosition.x + x,
Number.isNaN(this.point.x)
? undefined
: this.cameraPosition.x + this.point.x
);
this.yQuickToTween = this.yQuickTo(
this.cameraPosition.y + y,
Number.isNaN(this.point.y)
? undefined
: this.cameraPosition.y + this.point.y
);
this.point.set(NaN, NaN);
}
}
}
export class CameraShake extends Shake {
constructor(...params: ConstructorParameters<typeof Shake>) {
super(...params);
this.dom.addEventListener("mousedown", this.selfMouseDown);
}
mouseDown = () => {
this.pause = true;
this.dom.addEventListener("mouseup", this.selfMouseUp, {
once: true,
});
};
selfMouseDown = () => this.mouseDown.call(this);
mouseUp = () => {
//鼠标按下又抬起未移动 point是NaN不能参与计算 也不需要计算
if (Number.isNaN(this.point.x)) return (this.pause = false);
gsap.to(this.camera.position, {
x: this.cameraPosition.x + this.point.x,
y: this.cameraPosition.y + this.point.y,
z: this.cameraPosition.z,
duration: 0.5,
onComplete: () => {
this.pause = false;
},
});
};
selfMouseUp = () => this.mouseUp.call(this);
destroyMouseEvent = () => {
this.destroyMouseMove();
this.dom.removeEventListener("mousedown", this.selfMouseDown);
};
}
gitee仓库:
CameraShake.ts文章来源地址https://www.toymoban.com/news/detail-545941.html
到了这里,关于THREE.JS镜头随鼠标晃动效果的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!