ray
ray

Reputation: 261

Mouse & Touch horizontal div sliding

I'm trying to make a feature for my website which allows user to horizontally scroll div content using mousemove and touchmove events (it's similar to Apple AppStore any app Screenshots section). I'm doing this by setting a negative margin-left property for first child. Everything is working except that when user on mobile device touches screen or on desktop moves cursor into parent div, it's content not scrolling from where currently is but it's "jumping". I tried to do this by adding current margin-left value to new pageX value, but it's not working. What I'm doing wrong? Or maybe it's not possible to do this feature this way?

JS/Babel code:

class Collage {
  constructor(selector) {
    this.selector = $(selector);
    this.children = this.selector.children(':first-child');
    this.selector.on('mousemove touchmove', this.onMove.bind(this));
  }

  onMove(event) {
    const marginLeft = parseFloat(this.children.css('margin-left'));
    let mouseX = event.pageX || -event.touches[0].pageX || -event.changedTouches[0].pageX;

    const margin = calculateMargin(mouseX, 1);
    this.children.css('margin-left', margin);

    function calculateMargin(value, speed) {
      let val = -value*speed;
      return val <= 0 ? val : 0;
    }
  }
}

SCSS code:

.collage {
  overflow: hidden;
  max-height: 300px;

  white-space: nowrap;

  font-size: 0;

  img {
    display: inline-block;
    &:first-of-type {
      margin-left: -20%;
    }
  }
}

My fiddle

Thanks in advance for your answers.

Upvotes: 0

Views: 3698

Answers (2)

ray
ray

Reputation: 261

I handle with this problem. With Matthew Levy's answer I figured out that it can be done more easier. So I put whole content into nested container and my CSS (SCSS) code looks like below:

.collage {
  overflow: hidden;
  -webkit-overflow-scrolling: touch;

  width: 100%;
  max-height: 300px;

  font-size: 0;  

  .wrapper {
    width: 100%;
    height: 100%;
    overflow: auto;
    padding: 0;
    white-space: nowrap;
  }

  img {
    display: inline-block;
    &:first-of-type {
      // transition prevent jumping when user move cursor into container
      transition: margin .8s;
    }
  }
}

With this code I don't need a touchmove event in JS. So now my JS code looks like below (btw, I optimalized this code in regard to the last one):

class Collage {
  constructor(selector) {
    this.selector = $(selector);
    this.child = this.selector.children();

    const offsetHeight = this.child[0].offsetHeight;
    const clientHeight = this.child[0].clientHeight;
    // hide scrollbar - it can be done with CSS but in various browsers scrollbar may has various size so using JS to deal with this is safier
    this.selector.css('margin-top', -(offsetHeight - clientHeight));
    this.child.css('margin-top', offsetHeight - clientHeight);

    this.child.on('mousemove', this.onMove.bind(this));

    this.mouseX = 0;
    this.marginLeft = 0;

    this.selectorWidth = this.selector.width();
    this.selectorLeft = this.selector.position().left;
  }

  onMove(event) {
    // calculate new margin value in percentages
    const calculateMargin = () => parseInt((100 * (this.mouseX - this.selectorLeft)) / this.selectorWidth);

    this.mouseX = event.pageX;
    // limit a new value between 0/100%
    this.marginLeft += (Math.min(Math.max(calculateMargin(), 0), 100) - this.marginLeft);
    this.child.children(':first-child').css('margin-left', `-${this.marginLeft}%`);
  }
}

Working fiddle

Upvotes: 1

Matthew Levy
Matthew Levy

Reputation: 96

Can you just get what you want without Javascript by creating a second wrapper element around the images?

<div class="collage">
  <div class="wrapper">
    <img /> ....
 </div>
</div>

Then CSS

.collage {
  overflow-x: scroll;
  -webkit-overflow-scrolling: touch;
  width: 100%;
  display: block;
  position: relative;
}
.wrapper {
   width: 10000px; 
  /* set width to something large 
     or set with Javascript to the calculated width of your images */
}

Upvotes: 1

Related Questions