The recent project needs to make a function to select seats and book tickets on the mobile terminal. I searched the Internet and couldn’t find a ready-made library to address my needs, so I wrote a rough version of it myself.

demand

  • The front end shows the seating distribution
  • Drag to zoom in and out and click to select seats

Technology stack

  • svg
  • hammerjs

rendering

Detailed effect can go to demo that experience, suggest to use mobile phone experience.

svg

The SVG of the seating map is exported after the UI is drawn, and the entire SVG tag and its contents are returned via the back-end interface. The front end simply requests the SVG and inserts it. After insertion, you need to black the ordered seat position, which cannot be selected.

hammerjs

Hammerjs is a library of gesture events including TAP, doubletap, Press, PAN, swipe, Pinch, and Rotate. At the same time, it also provides a wealth of custom configurations that allow you to fulfill the requirements of each nAO (DONG) and each DA (KAI).

usage

let hammertime = new Hammer(myElement, myOptions);
hammertime.on('pan'.function(ev) {
	console.log(ev)
})
Copy the code

HTML structure

Svg-box for pinching (scaling)

SVG for offset

<div class="ticket-map">
    <div class=svg-box>
        <svg>.</svg>
    </div>
</div>
Copy the code

Initialize the

Set a variable to record the attribute change after the gesture operation

// Record the displacement variable
let transform = {
    svgScale: 0.5.// SVG defaults to zoom
    scale: 1./ / SVG - box zoom
    maxScale: 7.// SVG -box maximum zoom
    minScale: 1.// svG-box minimizes zoom
    translateX: 0.// SVG X offset
    translateY: 0.// SVG is Y offset
    minX: 0.// SVG minimum X offset
    maxX: 0.// SVG maximum X offset
    minY: 0.// SVG minimum Y offset
    maxY: 0  // SVG maximum Y offset
}
Copy the code

Since the SVG provided by the UI is slightly larger at 1000*715, svgScale: 0.5 is scaled to fit the screen.

Once you get the SVG you need to center it and calculate the drag boundary (minX/Y maxX/Y)

    let svgTarget = document.querySelector('svg')
    let svgBox = document.querySelector('.svg-box'TranslateX = math.round ((svgbox.clientWidth -svgtarget.clientWidth) / 2) // The X offset when vertical center is transform.translatey = math.round ((svgbox.clientheight - svgtarget.clientheight) / 2) // Y offset when vertical center transform.minx = transform.translatex - svgBox.clientWidth / 4 transform.maxX = transform.translateX + svgBox.clientWidth / 4 transform.minY = Transform. maxY = transform.translateY + svgbox. clientHeight / 2.5 transform.translateY = transform.translateY + svgbox. clientHeight / 2.5 svgTarget.style.transform = `translate(${transform.translateX}px, ${transform.translateY}px) scale(${transform.svgScale}) `Copy the code

Limit the X-y offset of SVG to 1/4 of the width of an SVG-box and 1/2.5 of the height to prevent SVG from being dragged off-screen. You can adjust the scores based on the actual SVG.

Gestures configuration

  // Initializes the hammer object
  var svgHam = new Hammer(svgBox)
  svgHam.get('pinch').set({ enable: true })  Pinch recognizer setting pinchable (zooming in and out gestures) does not listen by default
  svgHam.get('pan').set({ direction: Hammer.DIRECTION_ALL }) // Return pan identifier to set drag direction to all directions
Copy the code

Listen for an SVG-box in case the event is not triggered after the SVG moves

Knead put event

svgHam.on('pinchstart pinchmove', (e) => {
        let { scale, maxScale, minScale } = transform
        scale *= e.scale
        scale = scale >= maxScale ? maxScale : scale
        scale = scale <= minScale ? minScale : scale
        transform.scale = scale
        svgBox.style.transform = `scale(${scale}) `
      })
Copy the code

Set maxScale and minScale to avoid infinity or infinitesimal

· Note that you are scaling the box and not the SVG itself, otherwise you will find that the entire SVG is out of place after dragging and scaling

Drag events

function checkXY(x, y) {
    let { minX, minY, maxX, maxY } = transform
    x = x > maxX ? maxX : x
    x = x < minX ? minX : x
    y = y > maxY ? maxY : y
    y = y < minY ? minY : y
    return {
        x,
        y
    }   
}

svgHam.on('panstart panmove', (e) => {
    let { scale, translateX, translateY, svgScale } = transform
    let y = translateY + e.deltaY / scale
    let x = translateX + e.deltaX / scale
    let validXY = checkXY(x, y)
    svgTarget.style.transform = `translate(${validXY.x}px, ${validXY.y}px) scale(${svgScale})`
})

svgHam.on('panend', (e) => {
    let { scale, translateX, translateY} = transform
    let y = translateY + e.deltaY / scale
    let x = translateX + e.deltaX / scale
    let validXY = checkXY(x, y)
    transform.translateY = validXY.y
    transform.translateX = validXY.x
})
Copy the code

The offset is updated when the offset is complete, because hammer provides the offset value deltaY/deltaX that is the difference between the initial position of your finger click and the move.

The offset/scale is a great way to control the drag speed after scaling up, otherwise the entire SVG will run away after scaling up.

Choose a

With the above effect, seat selection is easy with a single click event

document.querySelector('.svg-box').addEventListener('click', selectSeat)
// SVG click the event to select the seat
function selectSeat (e) {
  if(e.target.tagName ! = ='circle') return false
  e.target.style.fill = e.target.style.fill === 'red' ? '#ccc' : 'red' // The selected seat turns red
  // do something...
}
Copy the code

The complete code


Write in the last

Not perfect, but also meet the needs of the present, I hope to give you a little inspiration. Inadequate places, but also hope that you gods guide the maze.