CSS Scroll Snap with Animation Effect

2019-08-29 03:31发布

问题:

[Precondition]

I am trying to make a number list picker that user can scroll on it. The selected number was supposed to snap to the center of the container. Once a number was selected, it should be enlarged and changed the color.


[Problems]

  • How can I snap the first or latest items to the center of the container?
  • Is there a CSS or JavaScript way to detect a number is snapping to the center then assign selected class on it?
  • Do not rely on any plugins.

* {
  box-sizing: border-box;
  font-family: Roboto, sans-serif;
}

.container {
  display: flex;
  flex-direction: row;
  width: 10rem;
  height: 22rem;
  border-radius: 3rem;
  border: solid 0.2rem #b2b2c2;
  background-color: #000000;
  display: flex;
  align-items: center;
  justify-content: center;
}

.scrollport {
  display: flex;
  flex-direction: column;
  flex-wrap: nowrap;
  width: 9.4rem;
  height: 22rem;
  overflow: auto;
  scroll-snap-type: y mandatory;
}

.cell {
  display: block;
  scroll-snap-align: center;
  flex-grow: 1;
  flex-shrink: 0;
  flex-basis: 33.3%;
  display: flex;
  justify-content: center;
  align-items: center;
  color: #e9e9f2;
  font-size: 2.4rem;
}

.selected {
  font-size: 3rem;
  font-weight: bold;
  color: #0073e6;
}
<div class="container">
  <div class="scrollport">
    <div class="cell">09</div>
    <div class="cell selected">10</div>
    <div class="cell">11</div>
    <div class="cell">12</div>
    <div class="cell">13</div>
    <div class="cell">14</div>
    <div class="cell">15</div>
    <div class="cell">16</div>
  </div>
</div>


[Expect Result]

回答1:

Add pseudo elements to .scrollport and style them like .cell

.scrollport:before,
.scrollport:after {
  content: '';
}
.scrollport:before,
.scrollport:after,
.cell {
  /* ... */
}

Also I created a JS function that utilizes David Walsh's debounce function to simulate a scrollend event. I then check for every cell if it's center is around the scrollport's center and set the classes accordingly

"use strict";
console.clear()

{
  const selector = 'scrollport'
  const selected = 'selected'
  const scrollports = document.getElementsByClassName(selector)

  for (const scrollport of scrollports) {
    scrollport.addEventListener('scroll', debounce(check, 250) /* simulate scrollend event */ )
  }

  function check(e) {
    // uses native elementFromPoint for better performance
    const rect = e.target.getBoundingClientRect();
    const centerCell = document.elementFromPoint(rect.left + e.target.offsetWidth / 2, rect.top + e.target.offsetHeight / 2)
    for (const cell of e.target.getElementsByClassName(selected)) {
      cell.classList.remove(selected)
    }
    centerCell.classList.add(selected)
    
    // Old version for backward compatibility
    // const rect = e.target.getBoundingClientRect();
    // for (const cell of e.target.children) {
    //   const cellRect = cell.getBoundingClientRect();
    //   const bounds = [rect.height/2 - cellRect.height/2, rect.height/2 + cellRect.height/2];
    //   const centerScrollOffset = cellRect.top - rect.top + cell.offsetHeight / 2
    //   if (bounds[0] < centerScrollOffset && centerScrollOffset < bounds[1]) {
    //     cell.classList.add(selected)
    //   } else {
    //     cell.classList.remove(selected)
    //   }
    // }
  }
}
// From: https://davidwalsh.name/javascript-debounce-function
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
function debounce(func, wait, immediate) {
  var timeout;
  return function() {
    var context = this,
      args = arguments;
    var later = function() {
      timeout = null;
      if (!immediate) func.apply(context, args);
    };
    var callNow = immediate && !timeout;
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
    if (callNow) func.apply(context, args);
  };
};
* {
  box-sizing: border-box;
  font-family: Roboto, sans-serif;
}

.container {
  display: flex;
  flex-direction: row;
  width: 10rem;
  height: 22rem;
  border-radius: 3rem;
  border: solid 0.2rem #b2b2c2;
  background-color: #000000;
  display: flex;
  align-items: center;
  justify-content: center;
}

.scrollport:before,
.scrollport:after {
  content: '';
}

.scrollport {
  display: flex;
  flex-direction: column;
  flex-wrap: nowrap;
  width: 9.4rem;
  height: 22rem;
  overflow: auto;
  scroll-snap-type: y mandatory;
}

.scrollport:before,
.scrollport:after,
.cell {
  display: block;
  scroll-snap-align: center;
  flex-grow: 1;
  flex-shrink: 0;
  flex-basis: 33.3%;
  display: flex;
  justify-content: center;
  align-items: center;
  color: #e9e9f2;
  font-size: 2.4rem;
}

.selected {
  font-size: 3rem;
  font-weight: bold;
  color: #0073e6;
}
<div class="container">
  <div class="scrollport">
    <div class="cell">09</div>
    <div class="cell selected">10</div>
    <div class="cell">11</div>
    <div class="cell">12</div>
    <div class="cell">13</div>
    <div class="cell">14</div>
    <div class="cell">15</div>
    <div class="cell">16</div>
  </div>
</div>



回答2:

You can try this code here:

in this answer used jquery malihu-custom-scrollbar-plugin link and update some code here

(function($){
    $(window).on("load",function(){
        $(".scrollport").mCustomScrollbar({autoHideScrollbar:true});
    });
})(jQuery);
* {
  box-sizing: border-box;
  font-family: Roboto, sans-serif;
}

.container {
  display: flex;
  flex-direction: row;
  width: 10rem;
  height: 22rem;
  border-radius: 3rem;
  border: solid 0.2rem #b2b2c2;
  background-color: #000000;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

.scrollport {
  display: flex;
  flex-direction: column;
  flex-wrap: nowrap;
  width: 9.4rem;
  height: 22rem;
  overflow: auto;
}

.cell {
  flex-basis: 120px;
  height: 120px;
  color: #e9e9f2;
  font-size: 2.4rem;
  display: flex; 
  align-items: center;
  justify-content: center;
  padding-left: 20px;
}

.selected {
  font-size: 3rem;
  font-weight: bold;
  color: #0073e6;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.2/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/malihu-custom-scrollbar-plugin/3.1.5/jquery.mCustomScrollbar.concat.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/malihu-custom-scrollbar-plugin/3.1.5/jquery.mCustomScrollbar.css" rel="stylesheet" />

<div class="container">
  <div class="scrollport">
    <div class="cell">09</div>
    <div class="cell selected">10</div>
    <div class="cell">11</div>
    <div class="cell">12</div>
    <div class="cell">13</div>
    <div class="cell">14</div>
    <div class="cell">15</div>
    <div class="cell">16</div>
  </div>
</div>