0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

slick.jsにピンチイン・アウト機能を追加する

Posted at

概要

Swiper.jsでは標準で実装されているZoom機能がslick.jsにはない。商品画像のスライド等でよく見るので、実装してみた。

条件

  • jquery: ^3.7.1
  • slick-carousel: "^1.8.1

コード

ピンチイン・アウトできるスライドに.is-zoomableを付与しておく。

index.html
<div class="js-slider">
    <div class="is-zoomable">your content</div>
    <div class="is-zoomable">your content</div>
    <div class="is-zoomable">your content</div>
</div>    
slider.js
cosnt $slider = $('.js-slider');
const option  = { /** オプション */ }

// Initialize Slick slider with options
$slider.slick(option);

$slider.on('touchstart', '.is-zoomable', function (e, slick) {
  onTouchStart(e, slick);
});

$slider.on('touchmove', '.is-zoomable', function (e) {
  onTouchMove(e);
});

$slider.on('touchend', '.is-zoomable', function (e, slick) {
  onTouchEnd(e, slick);
});

諸々の初期値

slider.js
/** @type {number} The current zoom scale */
let scale = 1;

/** @type {number} The maximum / minimum zoom scale */
let maxScale = 2;
let minScale = 1;

/** @type {number} The starting distance between two touch points for pinch zoom */
let startDistance = 0;

/** @type {number} The current distance between two touch points */
let currentDistance = 0;

/** @type {number} The X/Y coordinates of the center point of pinch zoom (relative to image) */
let originX = 0;
let originY = 0;

/** @type {number} The current horizontal/vertical translation of the image */
let posX = 0;
let posY = 0;

/** @type {number} The previous touch X/Y coordinates for dragging */
let prevX = 0;
let prevY = 0;

/** @type {boolean} Flag to indicate if pinch zoom / drag is in progress */
let isZooming = false;
let isDragging = false;

タッチポイント(指)の間の直線距離の計算

dxで二本の指の水平差分(距離の大きさと向き)、dyで二本の指の垂直差分を計算する。

slider.js
/**
 * Calculates the distance between two touch points on the screen.
 *
 * @param {TouchList} touches - The list of touch points from a touch event
 * @returns {number} The distance between the two touch points in pixels
 */
function calculateDistance(touches) {
    const dx = touches[0].clientX - touches[1].clientX;
    const dy = touches[0].clientY - touches[1].clientY;
    return Math.hypot(dx * dx + dy * dy);
}

画像をパン(平行移動)するときに、あらかじめ計算した「許容できる動きの範囲」を超えないように位置を抑え込む。

/**
 * Clamps a value within a specified range.
 *
 * @param {number} value - The value to be clamped
 * @param {number} maxDistance - The maximum allowed distance (the range)
 * @returns {number} The clamped value
 */
function limitPosition(value, maxDistance) {
    return Math.min(Math.max(value, -maxDistance), maxDistance);
}

/**
 * Applies the zoom and pan transformations to the image element.
 * @param {HTMLImageElement} img - The image element to apply the transformation to
 */
function updateImageTransform(img) {
    // Calculate the maximum allowable translation based on the image size and scale.
    const maxOffsetX = (img.clientWidth * (scale - 1)) / 2;
    const maxOffsetY = (img.clientHeight * (scale - 1)) / 2;
    
    // Clamp the translation values to ensure the image stays within bounds
    posX = limitPosition(posX, maxOffsetX);
    posY = limitPosition(posY, maxOffsetY);
    
    // When the zoom is reset (scale=1), add an animation to return the image to the center (0,0)
    if (scale === 1) {
      img.style.transition = 'transform 0.3s ease';
      posX = 0;
      posY = 0;
    } else {
      img.style.transition = 'none';
    }
    
    // Apply the transform (translation and scale) to the image
    img.style.transform = `translate(${posX}px, ${posY}px) scale(${scale})`;
    img.style.transformOrigin = `${originX}px ${originY}px`;
    }

Touchstart

ズーム/パン操作のためにスワイプ機能を無効化、e.touches.lengthでピンチ or ドラッグを判定し、初期値・中心点を記録する。
ピンチの中心を、ビューポート座標から画像の矩形 (getBoundingClientRect) を引くことで、画像内部のオフセットとして得る。
touches.length === 2 でピンチズーム開始処理、touches.length === 1 && scale > 1 でズーム中のドラッグパン開始。

slider.js
/**
* Handles the touchstart event.
*
* - Initiates pinch zoom with two fingers.
* - Initiates dragging with one finger if zoom is applied.
* @param {TouchEvent} e - The touch event
*/
function onTouchStart(e, slick) {
    const img = e.target;
    
    if (e.touches.length === 2) {
      startDistance = calculateDistance(e.touches);
      // Start pinch zoom
      isZooming = true;
      
      // Calculate the center point of the pinch
      const rect = img.getBoundingClientRect();
      originX = ((e.touches[0].clientX + e.touches[1].clientX) / 2) - rect.left;
      originY = ((e.touches[0].clientY + e.touches[1].clientY) / 2) - rect.top;
      
      // Set the initial position of the pinch
      prevX = (e.touches[0].clientX + e.touches[1].clientX) / 2;
      prevY = (e.touches[0].clientY + e.touches[1].clientY) / 2;
    
      // Disable swipe behavior during pinch zoom
      $(slick.$slider).slick('slickSetOption', 'swipe', false, true);
    } else if (e.touches.length === 1) {
       // Start dragging when zoom is applied
      isDragging = true;

      // Calculate the initial position of the touch
      prevX = e.touches[0].clientX;
      prevY = e.touches[0].clientY;
    }
}

Touchmove

「ズーム中なら倍率更新」「ドラッグ中なら平行移動更新」を行い、毎回updateImageTransformscaleposXposY に応じて位置制限(クランプ)とtransformを再適用する。

ピンチズーム

calculateDistance で距離を再取得 → 新旧距離の比率でscaleに掛ける。距離計測→倍率更新→再計算のループにより連続的にズームする。

ドラッグパン

1本指の移動量 (dx, dy) を posX, posY に累積。前回のタッチ座標 (prevX, prevY) を更新して差分を常に算出する。

slider.js
/**
* Handles the touchmove event.
*
* - Updates zoom scale during pinch zoom.
* - Updates position during drag movement.
* @param {TouchEvent} e - The touch event
*/
function onTouchMove(e) {
    const img = e.target;
    if (isZooming && e.touches.length === 2) {
      // Handle pinch zoom
      currentDistance = calculateDistance(e.touches);
      scale *= currentDistance / startDistance;
      startDistance = currentDistance;
    
      // Clamp the zoom scale between [minScale, maxScale]
      scale = Math.min(Math.max(minScale, scale), maxScale);
    
      updateImageTransform(img);
    } else if (isDragging && e.touches.length === 1 && scale > 1) {
      // Handle dragging (pan)
      const dx = e.touches[0].clientX - prevX;
      const dy = e.touches[0].clientY - prevY;

      // Update the position of the image
      posX += dx;
      posY += dy;

      // Update the previous touch position
      prevX = e.touches[0].clientX;
      prevY = e.touches[0].clientY;
    
      updateImageTransform(img);
    }
}

Touchend

scaleが「ほぼ元サイズ」だったら厳密に 1 に戻し、updateImageTransform内で中央へのアニメーション(transition)が発動するようにする。
ズーム/パン操作のために無効化していたスワイプ機能を、タッチ終了後に再有効化する。

slider.js
/**
* Handles the touchend and touchcancel events.
*
* - Finalizes the zoom and drag operations.
* - Resets zoom scale to 1 if the zoom is minimal.
* @param {TouchEvent} e - The touch event
*/
function onTouchEnd(e, slick) {
    const img = e.target;
    isZooming = false;
    isDragging = false;
    
     // If the zoom scale is near 1, reset it to 1
    scale <= 1.05 && (scale = 1);
    
    updateImageTransform(img);
    // Enable swipe behavior again after zooming and panning
    $(slick.$slider).slick('slickSetOption', 'swipe', true, true);
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?