如何使用背景定位和百分比值制作可拖动的背景图片?

3
我正在尝试使用背景位置和百分比值制作一个简单的可拖动背景。目前我已经成功实现了拖动功能,但是我无法找到正确的公式使图片能够以相同的速度跟随光标移动(如果有意义的话)。
以下是一个简单的示例(仅使用x轴):

const container = document.querySelector('div');
const containerSize = container.getBoundingClientRect();

let imagePosition = { x: 50, y: 50 };
let cursorPosBefore = { x: 0, y: 0 };
let imagePosBefore = null;
let imagePosAfter = imagePosition;

container.addEventListener('mousedown', function(event) {
  cursorPosBefore = { x: event.clientX, y: event.clientY };
  imagePosBefore = imagePosAfter; // Get current image position
});

container.addEventListener('mousemove', function(event) {
  if (event.buttons === 0) return;

  let newXPos = imagePosBefore.x + ((cursorPosBefore.x - event.clientX) * 100 / containerSize.width);
  newXPos = (newXPos < 0) ? 0 : (newXPos > 100) ? 100 : newXPos; // Stop at the end of the image
  
  imagePosAfter = { x: newXPos, y: imagePosition.y }; // Save position
  container.style.backgroundPosition = `${newXPos}% ${imagePosition.y}%`;
});
div {
  width: 400px;
  height: 400px;
  background-position: 50% 50%;
  background-size: cover;
  background-repeat: no-repeat;
  background-image: url('https://istack.dev59.com/5yqL8.webp');
  cursor: move;
  border: 2px solid transparent;
}

div:active {
  border-color: red;
}
<div></div>

如果我点击背景中的一个白色叉号并移动鼠标,那么叉号应该始终保持在光标下,直到我到达图像或容器的末端。
这可能只是一个数学问题,但由于百分比如何与background-position配合使用,让我有点困惑。有什么想法吗?
1个回答

5

我不知道确切的公式,但你需要考虑CSS背景的大小。

如果背景大小与容器div的大小相等,那么你的公式可以使用,但实际上并不是这样。你需要知道缩放级别(可以根据大小与div大小的比例计算)。

以下是当您拥有缩放级别时使用的公式:

container.addEventListener('mousemove', function(event) {
    event.preventDefault();

    const zoomAffector = (currentZoomLevel - 100) / 100; // 100% zoom = image as big as container
    if (zoomAffector <= 0) return; // Cant drag a image that is zoomed out

    let newXPos = imagePosBefore.x + ((imagePosBefore.x - event.pageX) / zoomAffector * 100;);
    newXPos = (newXPos < 0) ? 0 : (newXPos > 100) ? 100 : newXPos;
  
    imagePosAfter = { x: newXPos, y: imagePosition.y };
    container.style.backgroundPosition = `${newXPos}% ${imagePosition.y}%`;
});

要获取CSS背景图像的尺寸,可以查看这个问题:使用JavaScript获取CSS背景图像的大小?

以下是我使用链接问题中的一个答案尝试解决您的问题,并获取了图片的宽度(也为y轴完成):

const container = document.querySelector('div');
const containerSize = container.getBoundingClientRect();

let imagePosition = { x: 50, y: 50 };
let cursorPosBefore = { x: 0, y: 0 };
let imagePosBefore = null;
let imagePosAfter = imagePosition;


var actualImage = new Image();
actualImage.src = $('#img').css('background-image').replace(/"/g,"").replace(/url\(|\)$/ig, "");
actualImage.onload = function() {
    const zoomX = this.width / containerSize.width - 1;
    const zoomY = this.height / containerSize.height - 1;
    
    container.addEventListener('mousedown', function(event) {
      cursorPosBefore = { x: event.clientX, y: event.clientY };
      imagePosBefore = imagePosAfter; // Get current image position
    });

    container.addEventListener('mousemove', function(event) {
        event.preventDefault();

        if (event.buttons === 0) return;

        let newXPos = imagePosBefore.x + ((cursorPosBefore.x - event.clientX) / containerSize.width * 100 / zoomX);
        newXPos = (newXPos < 0) ? 0 : (newXPos > 100) ? 100 : newXPos;
        let newYPos = imagePosBefore.y + ((cursorPosBefore.y - event.clientY) / containerSize.height * 100 / zoomY);
        newYPos = (newYPos < 0) ? 0 : (newYPos > 100) ? 100 : newYPos;

        imagePosAfter = { x: newXPos, y: newYPos };
        container.style.backgroundPosition = `${newXPos}% ${newYPos}%`;
    });
}
#img {
  width: 400px;
  height: 200px;
  background-position: 50% 50%;
  background-image: url('https://istack.dev59.com/5yqL8.webp');
  cursor: move;
  border: 2px solid transparent;
}

#img:active {
  border-color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="img"></div>

稍微整理一下:

const container = document.querySelector('div');
const containerSize = container.getBoundingClientRect();

let imagePosition = { x: 50, y: 50 };
let cursorPosBefore = { x: 0, y: 0 };
let imagePosBefore = null;
let imagePosAfter = imagePosition;

// Helpers
const minMax = (pos) => (pos < 0) ? 0 : (pos > 100) ? 100 : pos;
const setNewCenter = (x, y) => {
  imagePosAfter = { x: x, y: y }; 
  container.style.backgroundPosition = `${x}% ${y}%`;
};

const getImageZoom = () => {
  return new Promise((resolve, reject) => {
    let actualImage = new Image();
    actualImage.src = $('#img').css('background-image').replace(/"/g,"").replace(/url\(|\)$/ig, "");
    actualImage.onload = function() {
      resolve({
        x: zoomX = this.width / containerSize.width - 1,
        y: zoomY = this.height / containerSize.height - 1
      });
    }
  });
}
    
const addEventListeners = (zoomLevels) => {container.addEventListener('mousedown', function(event) {
      cursorPosBefore = { x: event.clientX, y: event.clientY };
      imagePosBefore = imagePosAfter; // Get current image position
    });

    container.addEventListener('mousemove', function(event) {
        event.preventDefault();

        if (event.buttons === 0) return;

        let newXPos = imagePosBefore.x + ((cursorPosBefore.x - event.clientX) / containerSize.width * 100 / zoomLevels.x);
        let newYPos = imagePosBefore.y + ((cursorPosBefore.y - event.clientY) / containerSize.height * 100 / zoomLevels.y);

        setNewCenter(minMax(newXPos), minMax(newYPos));
    });
};

getImageZoom().then(zoom => addEventListeners(zoom));
    
#img {
  width: 400px;
  height: 200px;
  background-position: 50% 50%;
  background-image: url('https://istack.dev59.com/5yqL8.webp');
  cursor: move;
  border: 2px solid transparent;
}

#img:active {
  border-color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="img"></div>

或者回答你的后续问题:

const container = document.querySelector("div");
const containerSize = container.getBoundingClientRect();

let imagePosition = { x: 50, y: 50 };
let cursorPosBefore = { x: 0, y: 0 };
let imagePosBefore = null;
let imagePosAfter = imagePosition;

// Helpers
const minMax = (pos) => (pos < 0 ? 0 : pos > 100 ? 100 : pos);
const setNewCenter = (x, y) => {
  imagePosAfter = { x: x, y: y };
  container.style.backgroundPosition = `${x}% ${y}%`;
};

const getImageZoom = () => {
  return new Promise((resolve, reject) => {
    let actualImage = new Image();

    actualImage.src = $("#img")
      .css("background-image")
      .replace(/"/g, "")
      .replace(/url\(|\)$/gi, "");
    actualImage.onload = function () {
      const imgW = this.width,
        imgH = this.height,
        conW = containerSize.width,
        conH = containerSize.height,
        ratioW = imgW / conW,
        ratioH = imgH / conH;

      // Stretched to Height
      if (ratioH < ratioW) {
        resolve({
          x: imgW / (conW * ratioH) - 1,
          y: imgH / (conH * ratioH) - 1,
        });
      } else {
        // Stretched to Width
        resolve({
          x: imgW / (conW * ratioW) - 1,
          y: imgH / (conH * ratioW) - 1,
        });
      }
    };
  });
};

const addEventListeners = (zoomLevels) => {
  container.addEventListener("mousedown", function (event) {
    cursorPosBefore = { x: event.clientX, y: event.clientY };
    imagePosBefore = imagePosAfter; // Get current image position
  });

  container.addEventListener("mousemove", function (event) {
    event.preventDefault();

    if (event.buttons === 0) return;

    let newXPos =
      imagePosBefore.x +
      (((cursorPosBefore.x - event.clientX) / containerSize.width) * 100) /
        zoomLevels.x;
    let newYPos =
      imagePosBefore.y +
      (((cursorPosBefore.y - event.clientY) / containerSize.height) * 100) /
        zoomLevels.y;

    setNewCenter(minMax(newXPos), minMax(newYPos));
  });
};

getImageZoom().then((zoom) => addEventListeners(zoom));
#img {
  width: 400px;
  height: 200px;
  background-size: cover;
  background-position: 50% 50%;
  background-image: url('https://istack.dev59.com/5yqL8.webp');
  cursor: move;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="img"></div>


1
背景大小:转换不会有任何影响。示例仍然可以正常工作。@Arkellys,你仍然需要注意缩放问题。 - MauriceNino
是的,那绝对是我忘记的参数!我现在无法测试你的代码,所以如果它能正常工作,我会稍后回来接受你的答案,再次感谢! - Arkellys
没问题。@Arkellys - MauriceNino
我在谈论原则 - 它仍然有效。只是现在计算缩放的方式不同,因为有一个第二个参数生效 - size属性。cover意味着图像的宽度等于容器的宽度或者图像的高度等于容器的高度。有了这些信息和一些简单的数学运算,你可以计算出图像的实际高度和宽度。如果你不能做到这一点,请提出一个新问题,因为你的问题已经得到了正确的回答,后续问题不应该改变答案的内容。 - MauriceNino
对于误解,我很抱歉,但是我以为你的解决方案适用于我给出的示例,但事实并非如此。如果你只是在谈论“原则”,那么也许你添加的复杂示例并不必要,因为它们并没有真正解决我的问题,反而令人困惑。我还需要补充一点,我只是为了在Stack Overflow上提供这段代码片段,并不是我的实际代码。至于你最后的评论,我从未说过获取图像/背景大小是个问题,所以我不明白你为什么认为告诉我如何做这件事在这里是相关的。 - Arkellys
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接