Подтвердить что ты не робот

Элемент масштаба, пропорциональный обложке фона с помощью jQuery

У меня сложный вопрос: У меня есть полный профиль на сайте, над которым я работаю. Теперь я хочу привязать div к определенной позиции на изображении, а также, что div масштабируется так же, как и мое фоновое изображение с свойством "background-size: cover". Поэтому в этом примере у меня есть изображение города, которое охватывает окно браузера, и я хочу, чтобы мой div накладывал одно конкретное здание независимо от размера окна.

Мне уже удалось заставить div придерживаться одной позиции, но не могу изменить размер. Что я сделал до сих пор:

http://codepen.io/EmmieBln/pen/YqWaYZ

var imageWidth = 1920,
    imageHeight = 1368,
    imageAspectRatio = imageWidth / imageHeight,
    $window = $(window);

var hotSpots = [{
    'x': -160,
    'y': -20,
    'height': 400,
    'width': 300
}];

function appendHotSpots() {
    for (var i = 0; i < hotSpots.length; i++) {
        var $hotSpot = $('<div>').addClass('hot-spot');
        $('.container').append($hotSpot);
    }
    positionHotSpots();
}

function positionHotSpots() {
    var windowWidth = $window.width(),
        windowHeight = $window.height(),
        windowAspectRatio = windowWidth / windowHeight,
        $hotSpot = $('.hot-spot');

    $hotSpot.each(function(index) {
        var xPos = hotSpots[index]['x'],
            yPos = hotSpots[index]['y'],
            xSize = hotSpots[index]['width'],
            ySize = hotSpots[index]['height'],
            desiredLeft = 0,
            desiredTop = 0;

        if (windowAspectRatio > imageAspectRatio) {
            yPos = (yPos / imageHeight) * 100;
            xPos = (xPos / imageWidth) * 100;
            xSize = (xSize / imageWidth) * 1000;
            ySize = (ySize / imageHeight) * 1000;
        } else {
            yPos = ((yPos / (windowAspectRatio / imageAspectRatio)) / imageHeight) * 100;
            xPos = ((xPos / (windowAspectRatio / imageAspectRatio)) / imageWidth) * 100;
        }

        $(this).css({
            'margin-top': yPos + '%',
            'margin-left': xPos + '%',
            'width': xSize + 'px',
            'height': ySize + 'px'
        });

    });
}

appendHotSpots();
$(window).resize(positionHotSpots);

Моя идея: Если (imageWidth/windowWidth) < 1 затем установите значение для var Scale = (windowWidth/imageWidth) else var Scale (windowHeight/imageHeight) и использовать шкалу var Scale for transform: scale (Scale, Scale) но я не смог справиться с этой работой...

Возможно, вы, ребята, могли мне помочь...

4b9b3361

Ответ 1

Решение для фонового размера: обложка

Я пытаюсь дать вам решение (или рассмотреть идею). Вы можете проверить рабочую демонстрацию здесь. Измените размер окна, чтобы увидеть результат.

Во-первых, я не понял, почему вы используете transform, top:50% и left:50% для горячей точки. Поэтому я попытался решить эту проблему, используя минимальный прецедент, и скорректировал вашу разметку и CSS для удобства.

Здесь rImage - соотношение сторон исходного изображения.

 var imageWidth = 1920;
 var imageHeight = 1368;
 var h = {
   x: imageWidth / 2,
   y: imageHeight / 2,
   height: 100,
   width: 50
 };
 var rImage= imageWidth / imageHeight;

В обработчике изменения размера окна вычислите аспектный ракурс окна просмотра r. Далее, трюк заключается в том, чтобы найти размеры изображения при изменении размера окна. Но, видовое окно будет обрезать изображение, чтобы сохранить соотношение сторон. Поэтому для вычисления размеров изображения нам нужна какая-то формула.

При использовании background-size:cover для вычисления размеров изображения используются ниже формулы.

if(actual_image_aspectratio <= viewport_aspectratio)
    image_width = width_of_viewport
    image_height = width_ofviewport / actual_image_aspectratio 

и

if(actual_image_aspectratio > viewport_aspectratio)
    image_width = height_of_viewport * actual_image_aspectratio 
    image_height = height_of_viewport

Вы можете сослаться на этот URL для лучшего понимания при расчете размеров изображений при использовании background-size:cover.

После получения размеров изображения нам нужно построить координаты "горячей точки" с фактического изображения на новые размеры изображения.

Для соответствия изображению в изображении видового экрана будут обрезаны сверху и снизу/слева и справа от изображения. Поэтому мы должны рассматривать этот размер отсеченного изображения как смещение при построении горячих точек.

offset_top=(image_height-viewport_height)/2
offset_left=(image_width-viewport_width)/2

добавить эти значения смещения в каждую точку доступа x,y

var imageWidth = 1920;
var imageHeight = 1368;
var hotspots = [{
  x: 100,
  y: 200,
  height: 100,
  width: 50
}, {
  x: 300,
  y: 500,
  height: 200,
  width: 100
}, {
  x: 600,
  y: 600,
  height: 150,
  width: 100
}, {
  x: 900,
  y: 550,
  height: 100,
  width: 25
}];
var aspectRatio = imageWidth / imageHeight;

$(window).resize(function() {
  positionHotSpots();
});
var positionHotSpots = function() {
  $('.hotspot').remove();
  var wi = 0,
    hi = 0;
  var r = $('#image').width() / $('#image').height();
  if (aspectRatio <= r) {
    wi = $('#image').width();
    hi = $('#image').width() / aspectRatio;
  } else {
    wi = $('#image').height() * aspectRatio;
    hi = $('#image').height();
  }
  var offsetTop = (hi - $('#image').height()) / 2;
  var offsetLeft = (wi - $('#image').width()) / 2;
  $.each(hotspots, function(i, h) {

    var x = (wi * h.x) / imageWidth;
    var y = (hi * h.y) / imageHeight;

    var ww = (wi * (h.width)) / imageWidth;
    var hh = (hi * (h.height)) / imageHeight;

    var hotspot = $('<div>').addClass('hotspot').css({
      top: y - offsetTop,
      left: x - offsetLeft,
      height: hh,
      width: ww
    });
    $('body').append(hotspot);
  });
};
positionHotSpots();
html,
body {
  height: 100%;
  padding: 0;
  margin: 0;
}
#image {
  height: 100%;
  width: 100%;
  background: url('https://upload.wikimedia.org/wikipedia/commons/thumb/0/08/Alexanderplatz_Stadtmodell_1.jpg/1920px-Alexanderplatz_Stadtmodell_1.jpg');
  background-size: cover;
  background-repeat: no-repeat;
  background-position: center;
}
.hotspot {
  position: absolute;
  z-index: 1;
  background: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id='image'></div>

Ответ 2

Хорошо, так что мало кто знает о модулях CSS vh и vw (имеется в виду ViewportHeight и ViewportWidth). Я создал скрипт, который запускается один раз при загрузке страницы (в отличие от некоторых других ответов, которые запускаются при при каждом изменении размера).

Он вычисляет соотношение фонового изображения, добавляет два правила CSS в overlayContainer и готово.

Там также есть div #square, цель которого состоит в том, чтобы у нас был контейнер с соотношением 1:1 в качестве холста. Это соотношение гарантирует, что при создании наложенных элементов вертикальные и горизонтальные процентные расстояния одинаковы.

Для background-size: cover см. эту скрипку.

Для background-size: contain см. эту скрипку.

HTML-код:

<div id="overlayContainer">
  <div id="square">
    <!-- Overlaying elements here -->
  </div>
</div>

CSS:

#overlayContainer{
  position: absolute; /* Fixed if the background-image is also fixed */
  min-width:  100vw; /* When cover is applied */
  min-height: 100vh; /* When cover is applied */
  max-width:  100vw; /* When contain is applied */
  max-height: 100vh; /* When contain is applied */
  top:  50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

#square{
  position: relative;
  padding-bottom: 100%;
}

/* When placing overlaying elements, make them all absolutely positioned, and work with percentages only */
/* Look at my Fiddles for examples */

JavaScript (jQuery):

var image = new Image()
image.src = $('body').css('background-image').replace(/url\((['"])?(.*?)\1\)/gi,'$2').split(',')[0]

/* When cover is applied, use this: */
$('#overlayContainer').css({'height':100/(image.width/image.height)+'vw','width':100/(image.height/image.width)+'vh'})

/* When contain is applied, use this: */
$('#overlayContainer').css({'height':100*(image.height/image.width)+'vw','width':100*(image.width/image.height)+'vh'})

Надеюсь, это поможет


Обновление от @LGSon

Я не ожидал найти решение только для CSS, хотя здесь оно скрывается в этом ответе, и поэтому я решил добавить его в то же самое.

Если добавить эти 2 строки в правило #overlayContainer (работает как для cover, так и для contain), сценарий можно будет удалить.

width:  calc(100vh * (1920 / 1368));
height: calc(100vw * (1368 / 1920));

Конечно, версия скрипта имеет то преимущество, что автоматически получает значения, хотя, поскольку точка доступа имеет определенную точку расположения на фоне, размер изображения, скорее всего, будет известен.

Образец с background-size: cover

html, body {
  height: 100%;
  overflow: hidden;
}

body {
  margin: 0;
  background-image: url('https://upload.wikimedia.org/wikipedia/commons/thumb/0/08/Alexanderplatz_Stadtmodell_1.jpg/1920px-Alexanderplatz_Stadtmodell_1.jpg');
  background-size: cover;
  background-repeat: no-repeat;
  background-position: center;
}

#overlayContainer {
  position: absolute;
  width:  calc(100vh * (1920 / 1368));
  height: calc(100vw * (1368 / 1920));
  min-width:  100vw;     /*  for cover    */
  min-height: 100vh;     /*  for cover    */
  /* max-width:  100vw;      for contain  */
  /* max-height: 100vh;      for contain  */
  top:  50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

#square {
  position: relative;
  padding-bottom: 100%;
}

#square div {
  position: absolute;
  top: 19.75%;
  left: 49.75%;
  width: 4.75%;
  height: 4.75%;
  background-color: rgba(255,0,0,.7);
  border-radius: 50%;
}
<div id="overlayContainer">
  <div id="square">
    <div></div>
  </div>
</div>

Ответ 3

Хорошо, поэтому я попытался использовать вашу оригинальную идею и изменил только несколько бит здесь и там.

Вместо использования процентов, мне было проще использовать значения пикселей. Итак:

$(this).css({
  'margin-top': yPos + 'px',
  'margin-left': xPos + 'px',
  'width': xSize + 'px',
  'height': ySize + 'px'
});

Затем все, что нам нужно сделать, это проверить пропорцию видового экрана, чтобы увидеть, как нам нужно изменить свойства div

if (windowAspectRatio > imageAspectRatio) {
  var ratio = windowWidth / imageWidth;
} else {
  var ratio = windowHeight / imageHeight;
}

xPos = xPos * ratio;
yPos = yPos * ratio;
xSize = xSize * ratio;
ySize = ySize * ratio;

Рабочий пример: http://codepen.io/jaimerodas/pen/RaGQVm

Фрагмент стопки

var imageWidth = 1920,
    imageHeight = 1368,
    imageAspectRatio = imageWidth / imageHeight,
    $window = $(window);

var hotSpots = [{
  x: -210,
  y: -150,
  height: 250,
  width: 120
}, {
  x: 240,
  y: 75,
  height: 85,
  width: 175
}];

function appendHotSpots() {
  for (var i = 0; i < hotSpots.length; i++) {
    var $hotSpot = $('<div>').addClass('hot-spot');
    $('.container').append($hotSpot);
  }
  positionHotSpots();
}



function positionHotSpots() {
  var windowWidth = $window.width(),
    windowHeight = $window.height(),
    windowAspectRatio = windowWidth / windowHeight,
    $hotSpot = $('.hot-spot');

  $hotSpot.each(function(index) {
    var cambio = 1,
        xPos = hotSpots[index]['x'],
        yPos = hotSpots[index]['y'],
        xSize = hotSpots[index]['width'],
        ySize = hotSpots[index]['height'],
        desiredLeft = 0,
        desiredTop = 0;
    
    if (windowAspectRatio > imageAspectRatio) {
      var ratio = windowWidth / imageWidth;
    } else {
      var ratio = windowHeight / imageHeight;
    }
    
    xPos = xPos * ratio;
    yPos = yPos * ratio;
    xSize = xSize * ratio;
    ySize = ySize * ratio;

    $(this).css({
      'margin-top': yPos + 'px',
      'margin-left': xPos + 'px',
      'width': xSize + 'px',
      'height': ySize + 'px'
    });

  });
}

appendHotSpots();
$(window).resize(positionHotSpots);
html, body {
  margin: 0;
  width: 100%;
  height: 100%;
}

.container {
  width: 100%;
  height: 100%;
  position: relative;
  background-image: url(https://upload.wikimedia.org/wikipedia/commons/thumb/0/08/Alexanderplatz_Stadtmodell_1.jpg/1920px-Alexanderplatz_Stadtmodell_1.jpg);
  background-size: cover;
  background-repeat: no-repeat;
  background-position: center;
}

.hot-spot {
  background-color: red;
  border-radius: 0;
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 1;
  opacity: 0.8;
  content: "";
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container"></div>

Ответ 4

Опираясь на css-преобразования и применяя его к одному элементу, вы получаете гораздо лучшую производительность независимо от количества горячих точек (меньшее количество манипуляций с DOM и гораздо меньше повторных потоков). Аппаратное ускорение также приятно иметь:)

Сначала мета-код:

  • Создайте .hot-spot--container внутри вашего изображения .container

  • Создайте .hot-spot и поместите/размер их в пределах .hot-spot--container

  • Трансформация .hot-spot--container mimicking background-size: cover поведение

  • Повторяйте # 3, когда есть размер ребра

Рассчитайте коэффициент изображения bg:

var bgHeight = 1368;
var bgWidth = 1920;
var bgRatio = bgHeight / bgWidth;

Всякий раз, когда окно изменяет размер, пересчитайте соотношение контейнеров:

var containerHeight = $container.height();
var containerWidth = $container.width();
var containerRatio = containerHeight / containerWidth;

Рассчитать масштабные коэффициенты для имитации поведения background-size: cover...

if (containerRatio > bgRatio) {
    //fgHeight = containerHeight
    //fgWidth = containerHeight / bgRatio
    xScale = (containerHeight / bgRatio) / containerWidth
} else {
    //fgHeight = containerWidth / bgRatio
    //fgWidth = containerWidth 
    yScale = (containerWidth * bgRatio) / containerHeight
}

... и примените преобразование к элементу контейнера с горячей точкой, существенно изменив размер и повторно разместив его "синхронно" с фоном:

var transform = 'scale(' + xScale + ', ' + yScale + ')';

$hotSpotContainer.css({
    'transform': transform
});

Спрятать: https://jsfiddle.net/ovfiddle/a3pdLodm/ (вы можете играть с окном предварительного просмотра довольно эффективно. Обратите внимание, что код можно настроить так, чтобы принимать пиксельные размеры и позиционирование для горячих точек, вам нужно будет рассмотреть размеры контейнера и изображения при вычислении значений шкалы)

Обновление: поведение background-size: contain использует тот же расчет, за исключением случаев, когда containerRatio меньше, чем bgRatio. Обновление фона css и перелистывание знака достаточно.

Ответ 5

Ниже представлено решение jQuery, плагин bgCoverTool переопределяет элемент, основанный на масштабе исходного фонового изображения.

//bgCoverTool Properties
$('.hot-spot').bgCoverTool({
  parent: $('#container'),
  top: '100px',
  left: '100px',
  height: '100px',
  width: '100px'})

Демо:

$(function() {
  $('.hot-spot').bgCoverTool();
});
html,
body {
  height: 100%;
  padding: 0;
  margin: 0;
}
#container {
  height: 100%;
  width: 100%;
  background: url('https://upload.wikimedia.org/wikipedia/commons/thumb/0/08/Alexanderplatz_Stadtmodell_1.jpg/1920px-Alexanderplatz_Stadtmodell_1.jpg');
  background-size: cover;
  background-repeat: no-repeat;
  position: relative;
}
.hot-spot {
  position: absolute;
  z-index: 1;
  background: red;
  left: 980px;
  top: 400px;
  height: 40px;
  width: 40px;
  opacity: 0.7;
}
<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <title>BG Cover Tool</title>
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
  <script type="text/javascript" charset="utf-8">
    //bgCoverTool jQuery plugin
    (function($) {
      $.bgCoverTool = function(element, options) {
        var $element = $(element),
          imgsize = {};
        var defaults = {
          parent: $element.parent(),
          top: $element.css('top'),
          left: $element.css('left'),
          height: $element.css('height'),
          width: $element.css('width')
        };
        var plugin = this;
        plugin.settings = {};
        plugin.init = function() {
          plugin.settings = $.extend({}, defaults, options);
          var tempurl = plugin.settings.parent.css('background-image').slice(4, -1)
          .replace('"', '').replace('"', '');
          var tempimg = new Image();
          var console = console || {
            error: function() {}
          };
          if (plugin.settings.parent.css('background-size') != "cover") {
            return false;
          }
          if (typeof tempurl !== "string") {
            return false;
          }
          if (plugin.settings.top == "auto" || plugin.settings.left == "auto") {
            console.error("#" + $element.attr('id') + " needs CSS values for 'top' and 'left'");
            return false;
          }
          $(tempimg).on('load', function() {
            imgsize.width = this.width;
            imgsize.height = this.height;
            imageSizeDetected(imgsize.width, imgsize.height);
          });
          $(window).on('resize', function() {
            if ('width' in imgsize && imgsize.width != 0) {
              imageSizeDetected(imgsize.width, imgsize.height);
            }
          });
          tempimg.src = tempurl;
        };
        var imageSizeDetected = function(w, h) {
          var scale_h = plugin.settings.parent.width() / w,
            scale_v = plugin.settings.parent.height() / h,
            scale = scale_h > scale_v ? scale_h : scale_v;
          $element.css({
            top: parseInt(plugin.settings.top, 10) * scale,
            left: parseInt(plugin.settings.left, 10) * scale,
            height: parseInt(plugin.settings.height, 10) * scale,
            width: parseInt(plugin.settings.width, 10) * scale
          });

        };
        plugin.init();
      };
      /**
       * @param {options} object Three optional properties are parent, top and left.
       */
      $.fn.bgCoverTool = function(options) {
        return this.each(function() {
          if (undefined == $(this).data('bgCoverTool')) {
            var plugin = new $.bgCoverTool(this, options);
            $(this).data('bgCoverTool', plugin);
          }
        });
      }
    })(jQuery);
  </script>
</head>

<body>
  <div id="container">
    <div class="hot-spot"></div>
  </div>
</body>

</html>

Ответ 6

Более простой/лучший подход к вашей проблеме заключается в использовании элемента SVG, который лучше подходит для вашего требования. Замечательная вещь о SVG - все будет пропорционально масштабироваться по умолчанию, потому что это векторный объект, а не объект потока документа.

Этот пример продемонстрирует технику

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title>SVG</title>
        <style type="text/css" media="screen">
            body {
                background: #eee;
                margin: 0;
            }
            svg {
                display: block;
                border: 1px solid #ccc;
                position: absolute;
                top: 0;
                left: 0;
                width: 100%;
                height: 100%;
                background: #fff;
            }
            .face {
                stroke: #000;
                stroke-width: 20px;
                stroke-linecap: round
            }
        </style>
    </head>
    <body>
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="-350 -250 700 500">
            <circle r="200" class="face" fill="red"/>
            <path fill="none" class="face" transform="translate(-396,-230)" d="M487.41,282.411c-15.07,36.137-50.735,61.537-92.333,61.537 c-41.421,0-76.961-25.185-92.142-61.076"/>
            <circle id="leftEye" cx="-60" cy="-50" r="20" fill="#00F"/>
            <circle id="rightEye" cx="60" cy="-50" r="20" fill="#00F"/>
        </svg>
        <script type="text/javascript">
            document.getElementById('leftEye').addEventListener('mouseover', function (e) {
                alert('Left Eye');
            });
            document.getElementById('rightEye').addEventListener('mouseover', function (e) {
                alert('Right Eye');
            });
        </script>
    </body>
</html>

Вы можете добавлять изображения в SVG для достижения того, что вам нужно.

https://jsfiddle.net/tnt1/3f23amue/