Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Zooming in overflow: scroll

Tags:

css

I am trying to implement correctly scaling and zooming in css way. I created an example with scaled view. When click, the view should be zoomed and then to be able to scroll.

https://jsfiddle.net/opb5tcy8/4/

I have several issues with it:

  1. Can I somehow get rid of the margin-left and margin-top on the .zoomed class? I did not manage to scale it without necessity to shift it with these margins.
  2. When clicked, I can get the click position by clientX. I would like to use it to fluently scroll to the clicked position during zooming. However I can't manage the scroll to be fluent and when removing the margin-left it is kind of jumpy and not nice.
  3. When you zoom in and move the scroll to the center and then zoom out, you can see the zoom is not nice as it first scrolls to the right. Is there a way to prevent it?
  4. When you scroll to corners in Chrome on OSX it tends do navigate back/forward in browser. Is there a way to prevent this behaviour?

UPDATE:

The first part can be solved with transform-origin: 0 0. The other issues stays mostly the same as it is demonstrated.

like image 448
Vojtěch Avatar asked Oct 19 '22 20:10

Vojtěch


2 Answers

Hm... I could say it is impossible to satisfy point 2 your condition with current browsers' support. The other are possible, as in this demo:

$(document).ready(function() {
  var windowHalfWidth = $("#window").width() / 2;
  var scalingFactor = 0.55;
  var throtte = false;
  
  $("#slider").click(function(event) {
    
    //Simple event throtte to prevent click spamming breaking stuff up
    if (throtte) return false;
    throtte = true;
    setTimeout(function() {
      throtte = false;
    }, 1000);
    
    var xSelf = event.pageX - $("#window").offset().left + $("#window").scrollLeft();

    if ($(this).hasClass("zoomed")) {

      $("#window").animate({
        scrollLeft: (xSelf / scalingFactor - windowHalfWidth)
      }, 1000, "linear");
    } else {

      $("#window").animate({
        scrollLeft: (xSelf * scalingFactor - windowHalfWidth)
      }, 1000, "linear");
    }

    $("#slider").toggleClass("zoomed");
  });
});
body {
  background-color: #eee;
  margin-top: 10px; /*reduced margin for easier view in SO */
}

#window {
  width: 500px;
  height: 200px;
  margin: 0 auto;
  overflow-x: auto;
  overflow-y: hidden;
  border: 1px solid #999;
  position: relative;
  background-color: white;
}

#slider {
  width: 900px;
  height: 600px;
  background-color: #fff;
  position: absolute;
  transition: 1s linear;
  top: 0;
  left: 0;
  transform-origin: 0 0;
}

#slider.zoomed {
  transform: scale(0.55);
}

#slider div {
  width: 50px;
  height: 50px;
  line-height: 50px;
  position: absolute;
  top: 75px;
  background-color: #eee;
  text-align: center;
}

#obj1 {
  left: 10px;
}

#obj2 {
  left: 210px;
}

#obj3 {
  left: 410px;
}

#obj4 {
  left: 610px;
}

#obj5 {
  left: 810px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="window">
  <div id="slider" class="zoomed">
    <div id="obj1">1</div>
    <div id="obj2">2</div>
    <div id="obj3">3</div>
    <div id="obj4">4</div>
    <div id="obj5">5</div>
  </div>
</div>

As you can see, the zooming & scrolling is quite laggy, especially when the far right size is zoomed in.

The reason is simple, because jQuery and css both have their own animation loop, and they are not in sync. In order to solve this we'll need to somehow manage to do both scrolling & scaling animations with only one system, either jQuery or CSS.

Problem is: jQuery don't have a scaling feature, and css can't scroll elements. Wonderful.

If your scaling can be done with width/height though, it would be possible, using jquery width&height animate(). But if the #slider consists of many components I guess it can't be done.


So um writing an answer just to say it's impossible is kind of a let down, so I think maybe I can suggest an alternative, using dragging to scroll content (similar to the way Google map work):

var windowHalfWidth, startX, startLeft, minLeft, dragging = false,
  zooming = false;

var zoomElement = function(event) {
  var xSelf = event.pageX - $("#window").offset().left - parseFloat($("#slider").css("left"));

  if ($("#slider").hasClass("zoomed")) {

    minLeft = windowHalfWidth * 2 - 900;

    var newLeft = Math.min(Math.max((-(xSelf / 0.55 - windowHalfWidth)), minLeft), 0);
    $("#slider").css("left", newLeft + "px");
  } else {

    minLeft = windowHalfWidth * 2 - 900 * 0.55;

    var newLeft = Math.min(Math.max((-(xSelf * 0.55 - windowHalfWidth)), minLeft), 0);
    $("#slider").css("left", newLeft + "px");
  }

  $("#slider").toggleClass("zoomed");
}

$(document).ready(function() {
  windowHalfWidth = $("#window").width() / 2;
  minLeft = windowHalfWidth * 2 - 900 * 0.55;

  $("#slider").on({
    mousedown: function(event) {
      dragging = true;
      startX = event.pageX;
      startLeft = parseFloat($(this).css("left"));
    },
    mousemove: function(event) {
      if (dragging && !zooming) {
        var newLeft = Math.min(Math.max((startLeft + event.pageX - startX), minLeft), 0);
        $("#slider").css("left", newLeft + "px");
      }
    },
    mouseup: function(event) {
      dragging = false;

      if (Math.abs(startX - event.pageX) < 30 && !zooming) {
        // Simple event throtte to prevent click spamming
        zooming = true;
        $("#slider").css("transition", "1s");

        setTimeout(function() {
          zooming = false;
          $("#slider").css("transition", "initial");
        }, 1000);

        zoomElement(event);
      }
    },
    mouseleave: function() {
      dragging = false;
    }
  });
});
body {
  background-color: #eee;
  margin-top: 10px; /*reduced margin for easier view in SO */
}
#window {
  width: 500px;
  height: 200px;
  margin: 0 auto;
  overflow: hidden;
  border: 1px solid #999;
  position: relative;
  background-color: white;
}
#slider {
  width: 900px;
  height: 600px;
  background-color: #fff;
  position: absolute;
  top: 0;
  left: 0;
  transform-origin: 0 0;
}
#slider.zoomed {
  transform: scale(0.55);
}
#slider div {
  width: 50px;
  height: 50px;
  line-height: 50px;
  position: absolute;
  top: 75px;
  background-color: #eee;
  text-align: center;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}
#obj1 {
  left: 10px;
}
#obj2 {
  left: 210px;
}
#obj3 {
  left: 410px;
}
#obj4 {
  left: 610px;
}
#obj5 {
  left: 810px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div id="window">
  <div id="slider" class="zoomed">
    <div id="obj1">1</div>
    <div id="obj2">2</div>
    <div id="obj3">3</div>
    <div id="obj4">4</div>
    <div id="obj5">5</div>
  </div>
</div>

This variation manages to get CSS to do both animation, by sacrificing the scrollbar (which is pretty ugly imo, who needs it?) and use css left instead.

So I hope if in the end you can't find a good solution, at least you have this to consider as fall back version.

like image 86
AVAVT Avatar answered Oct 22 '22 08:10

AVAVT


I'll address the points individually and then give an example at the end.

When clicked, I can get the click position by clientX. I would like to use it to fluently scroll to the clicked position during zooming.

In my opinion scroll animations during transitions can be a bit choppy in webkit browsers. Try balancing the animation time of the jQuery effect with the animation time of the css transition.

When you zoom in and move the scroll to the centre and then zoom out, you can see the zoom is not nice as it first scrolls to the right. Is there a way to prevent it?

Bring the scrollLeft property of the div#window back to 0px. Again, tweaking the animation times will make this less jerky.

When you scroll to corners in Chrome on OSX it tends do navigate back/forward in browser. Is there a way to prevent this behaviour?

You could use the mouseover and mouseout events to toggle a overflow:hidden css on the body.

Here's an example change to your code:

var slider = $("#slider").on('click', function(event) {
  if (!slider.hasClass('zoomed')) {
    // zoom back to left position
    $('#window').animate({scrollLeft:'0px'});
  }else{
    // zoom to click position within slider
    $('#window').animate({scrollLeft:event.clientX + 'px'}, 2000);
  }
  slider.toggleClass("zoomed");
});

/* stop window scrolling when using slider */
slider
    .on('mouseover', function () {
        $(document.body).css({overflow:'hidden'});
    })
  .on('mouseout', function () {
        $(document.body).css({overflow:'auto'});
    });

And an updated fiddle.

like image 29
shennan Avatar answered Oct 22 '22 10:10

shennan