首页 > 解决方案 > CSS/Jquery 用鼠标平滑背景移动

问题描述

我试图从缩略图上的鼠标移动中获得平滑平移的缩放图像。

我尝试了所有类型的过渡持续时间、自定义补间函数、jquery animate,但都没有得到好的结果。

当缩略图很小时,跳过它的像素大到足以使图像交错。

jquery动画开始堆叠,css过渡突变,自定义函数,不知道如何轻松改变方向。

代码笔

const ZOOM_LEVEL = 2;

$(document).ready(function() {
  $(".thumb").mouseenter(enter);
  $(".thumb").mouseleave(leave);
  $('.thumb').mousemove(zoom);
});

function zoom(event) {
  const p = calculateZoomOverlay({x: event.pageX, y: event.pageY}, $(event.target));
  moveCursorOverlay(p.left, p.top);
  movePreviewBackground(p.offsetX, p.offsetY);
}

function calculateZoomOverlay(mouse, thumb) {
  let t = thumb.position();
  t.width = thumb.width();
  t.height = thumb.height();

  let z = {}; // Zoom overlay
  z.width = t.width / ZOOM_LEVEL;
  z.height = t.height / ZOOM_LEVEL;
  z.top = mouse.y - z.height / 2;
  z.left = mouse.x - z.width / 2;
  
  // Bounce off boundary
  if (z.top < t.top) z.top = t.top;
  if (z.left < t.left) z.left = t.left;
  if (z.top + z.height > t.top + t.height) z.top = t.top + t.height - z.height;
  if (z.left + z.width > t.left + t.width) z.left = t.left + t.width - z.width;

  z.offsetX = (z.left - t.left) / z.width * 100;
  z.offsetY = (z.top - t.top) / z.height * 100;

  return z;
}

function moveCursorOverlay(left, top) {
   $('.cursor-overlay').css({
    top: top,
    left: left
  });
}

function movePreviewBackground(offsetX, offsetY) {
  $('.preview').css({
    'background-position': offsetX + '% ' + offsetY + '%'
  });
}

function enter() {
  // Setup preview image
  const imageUrl = $(this).attr('src');
  const backgroundWidth = $('.preview').width() * ZOOM_LEVEL;
  $('.preview').css({
    'background-image': `url(${imageUrl})`,
    'background-size': `${backgroundWidth} auto`
  });
  $('.preview').show();

  $('.cursor-overlay').width($(this).width() / ZOOM_LEVEL);
  $('.cursor-overlay').height($(this).height() / ZOOM_LEVEL);
  $('.cursor-overlay').show();
}

function leave() {
  $('.preview').hide();
  $('.cursor-overlay').hide();
}
.image-container {
  padding: 5px;
  display: flex;
  flex-direction: row;
}

.thumbnail-container {
  display: flex;
  flex-direction: column;
}

.thumb {
  margin-bottom: 5px;
  width: 80px;
  height: 50px;
}

.thumb:hover {
  -moz-box-shadow: 0 0 5px orange;
  -webkit-box-shadow: 0 0 5px orange;
  box-shadow: 0 0 5px orange;
}

.preview {
  display: none;
  margin-left: 15px;
  width: 320px;
  height: 200px;
  border: 3px solid orange;
}

.cursor-overlay {
  display: none;
  background-color: rgba(0, 150, 50, 0.5);
  position: fixed;
  pointer-events: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="image-container">
  <div class="thumbnail-container">
    <img class="thumb" alt="thumbnail" src="https://i.imgur.com/sbrYaxH.jpg">
    <img class="thumb" alt="thumbnail" src="https://i.imgur.com/2PpkoRZ.jpg">
    <img class="thumb" alt="thumbnail" src="https://i.imgur.com/3lOTtJV.jpg">
  </div>

  <div class="cursor-overlay"></div>
  <div class="preview"></div>
</div>

标签: jquerycss

解决方案


在我看来,这是一种 CSS 过渡并不能解决问题的情况。它们通常不适合像这样的交互式动画。许多动画库也无法完成工作。

为了实现这种交互式动画,我喜欢使用以下等式:

loc += (destination - location) / 2

这是一个缓动方程 - 非常适合交互式动画。它可以比作芝诺的二分法悖论

在这里,我们得到了单个轴上的目的地和位置之间的差异,我们将差异减半并将其添加到当前位置。

当我们将此应用于您的代码时,我们最终得到:

 z.top += (dy - z.top) / SMOOTH;
 z.left += (dx - z.left) / SMOOTH;

为了获得更流畅的动画,我们设置了一个SMOOTH常数8。看看它最终的样子:

const ZOOM_LEVEL = 2;
const SMOOTH = 8; // how much smoothness/delay do you want on the animation

let thumb = $('.thumb');
$(document).ready(function() {
  thumb.mouseenter(enter);
  thumb.mouseleave(leave);
  thumb.mousemove(move);
});

let mouseX = 0;
let mouseY = 0; 
let z = { top: 0, left: 0 }; // Zoom overlay
let dx;
let dy;
function move(event) {
  mouseX = event.pageX;
  mouseY = event.pageY;
  thumb = $(event.target);
}

function loop() {
  const p = calculateZoomOverlay({x: mouseX, y: mouseY});
  moveCursorOverlay(p.left, p.top);
  movePreviewBackground(p.offsetX, p.offsetY);
  window.requestAnimationFrame(loop);
}
loop();

function calculateZoomOverlay(mouse) {
  let t = thumb.position();
  t.width = thumb.width();
  t.height = thumb.height();

  
  z.width = t.width / ZOOM_LEVEL;
  z.height = t.height / ZOOM_LEVEL;
  dy = mouse.y - z.height / 2;
  dx = mouse.x - z.width / 2;
  
  z.top += (dy - z.top) / SMOOTH;
  z.left += (dx - z.left) / SMOOTH;
  
  // Bounce off boundary
  if (z.top < t.top) z.top = t.top;
  if (z.left < t.left) z.left = t.left;
  if (z.top + z.height > t.top + t.height) z.top = t.top + t.height - z.height;
  if (z.left + z.width > t.left + t.width) z.left = t.left + t.width - z.width;

  z.offsetX = (z.left - t.left) / z.width * 100;
  z.offsetY = (z.top - t.top) / z.height * 100;

  return z;
}

function moveCursorOverlay(left, top) {
   $('.cursor-overlay').css({
    top: top,
    left: left
  });
}

function movePreviewBackground(offsetX, offsetY) {
  $('.preview').css({
    'background-position': offsetX + '% ' + offsetY + '%'
  });
}

function enter() {
  // Setup preview image
  const imageUrl = $(this).attr('src');
  const backgroundWidth = $('.preview').width() * ZOOM_LEVEL;
  $('.preview').css({
    'background-image': `url(${imageUrl})`,
    'background-size': `${backgroundWidth} auto`
  });
  $('.preview').show();

  $('.cursor-overlay').width($(this).width() / ZOOM_LEVEL);
  $('.cursor-overlay').height($(this).height() / ZOOM_LEVEL);
  $('.cursor-overlay').show();
}

function leave() {
  $('.preview').hide();
  $('.cursor-overlay').hide();
}
.image-container {
  padding: 5px;
  display: flex;
  flex-direction: row;
}

.thumbnail-container {
  display: flex;
  flex-direction: column;
}

.thumb {
  margin-bottom: 5px;
  width: 80px;
  height: 50px;
}

.thumb:hover {
  -moz-box-shadow: 0 0 5px orange;
  -webkit-box-shadow: 0 0 5px orange;
  box-shadow: 0 0 5px orange;
}

.preview {
  display: none;
  margin-left: 15px;
  width: 320px;
  height: 200px;
  border: 3px solid orange;
}

.cursor-overlay {
  display: none;
  background-color: rgba(0, 150, 50, 0.5);
  position: fixed;
  pointer-events: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="image-container">
  <div class="thumbnail-container">
    <img class="thumb" alt="thumbnail" src="https://i.imgur.com/sbrYaxH.jpg">
    <img class="thumb" alt="thumbnail" src="https://i.imgur.com/2PpkoRZ.jpg">
    <img class="thumb" alt="thumbnail" src="https://i.imgur.com/3lOTtJV.jpg">
  </div>

  <div class="cursor-overlay"></div>
  <div class="preview"></div>
</div>

您可以SMOOTH根据自己的喜好进行调整。

代码结构略有变化。而不是在鼠标移动时执行动画......我们现在使用 arequestAnimationFrame来不断计算我们的动画并响应变量的变化mouseX/Y。当用户停止移动鼠标时,这可以防止动画突然停止。

如果z.topz.left值都非常接近或等于dx和 ,则可以进行优化以结束计算dy。除非您处理更多计算,否则可能没有必要。


推荐阅读