├── image ├── panmove.gif └── pinch.gif ├── .gitignore ├── README.md └── index.html /image/panmove.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeCoffee/TicketMap/HEAD/image/panmove.gif -------------------------------------------------------------------------------- /image/pinch.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeCoffee/TicketMap/HEAD/image/pinch.gif -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw* 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 类似淘票票 选座功能(svg) 2 | 3 | > 最近项目需要在移动端做一个可以选座订票的功能。上网搜了一下没找到一个现成的库可以解决需求,所以就自己写了一个粗糙的版本。 4 | 5 | ### 需求 6 | - 前端展示座位分布 7 | - 可拖动 放大缩小 点击选座 8 | 9 | ### 技术栈 10 | - svg 11 | - [hammerjs](http://hammerjs.github.io/) 12 | 13 | ## 效果图 14 | 15 | ![捏放](https://user-gold-cdn.xitu.io/2018/11/15/16716a6e9ff860b0?w=261&h=279&f=gif&s=3473571) 16 | 17 | ![拖动](https://user-gold-cdn.xitu.io/2018/11/15/16716a607cf8620f?w=263&h=278&f=gif&s=2077812) 18 | 19 | 详细效果可以去[demo](https://hecoffee.github.io/TicketMap/)那体验一下,建议用手机端体验。 20 | 21 | ## svg 22 | 座位分布图的SVG是UI画好后导出来,并且通过后端接口返回整个SVG标签以及里面的内容。 23 | 前端只要发请求获得SVG并且插入就好了。插入后需要将已订购的座位置黑,无法选择。 24 | 25 | ## hammerjs 26 | [hammerjs](http://hammerjs.github.io/)是一个手势库,提供了tap, doubletap, press, pan, swipe, pinch以及 rotate等多种手势事件,同时也提供丰富的自定义配置可以让你完成产品各(nao)种(dong)各(da)样(kai)的需求。 27 | 28 | ### 用法 29 | 30 | ``` javaScript 31 | let hammertime = new Hammer(myElement, myOptions); 32 | hammertime.on('pan', function(ev) { 33 | console.log(ev) 34 | }) 35 | ``` 36 | 37 | ## html 结构 38 | 箱子svg-box用以捏放(缩放) 39 | 40 | svg用以偏移 41 | ```` html 42 |
43 |
44 | ..... 45 |
46 |
47 | ```` 48 | 49 | ## 初始化 50 | 设置一个变量用以记录手势操作后的属性变化 51 | 52 | ``` javascript 53 | // 记录位移变量 54 | let transform = { 55 | svgScale: 0.5, // svg 默认缩放 56 | scale: 1, // svg-box 缩放 57 | maxScale: 7, // svg-box 最大缩放 58 | minScale: 1, // svg-box 最小缩放 59 | translateX: 0, // svg X轴偏移 60 | translateY: 0, // svg Y轴偏移 61 | minX: 0, // svg 最小X轴偏移 62 | maxX: 0, // svg 最大X轴偏移 63 | minY: 0, // svg 最小Y轴偏移 64 | maxY: 0 // svg 最大Y轴偏移 65 | } 66 | ``` 67 | 因为UI提供的SVG是1000*715 略大,为了适应屏幕 作了svgScale: 0.5的缩放。 68 | 69 | 获取到SVG后需要进行居中 并且 计算出拖动边界(minX/Y maxX/Y) 70 | 71 | 72 | ``` 73 | let svgTarget = document.querySelector('svg') 74 | let svgBox = document.querySelector('.svg-box') 75 | transform.translateX = Math.round((svgBox.clientWidth - svgTarget.clientWidth) / 2) // 垂直居中时的X偏移 76 | transform.translateY = Math.round((svgBox.clientHeight - svgTarget.clientHeight) / 2) // 垂直居中时的Y偏移 77 | 78 | transform.minX = transform.translateX - svgBox.clientWidth / 4 79 | transform.maxX = transform.translateX + svgBox.clientWidth / 4 80 | transform.minY = transform.translateY - svgBox.clientHeight / 2.5 81 | transform.maxY = transform.translateY + svgBox.clientHeight / 2.5 82 | svgTarget.style.transform = `translate(${transform.translateX}px, ${transform.translateY}px) scale(${transform.svgScale})` 83 | 84 | ``` 85 | 以svg-box的width的4分之一,以及height的2.5分之一作为svg的X Y轴偏移量的极限,以免svg被拖动出屏幕之外。可以根据实际SVG调整分数。 86 | 87 | ### 手势配置 88 | 89 | ```` javascript 90 | // 初始化 hammer对象 91 | var svgHam = new Hammer(svgBox) 92 | svgHam.get('pinch').set({ enable: true }) // 返回pinch识别器 设置 可捏放 (放大缩小手势) 默认不监听 93 | svgHam.get('pan').set({ direction: Hammer.DIRECTION_ALL }) // 返回pan识别器 设置拖动方向为 所有方向 94 | ```` 95 | 监听svg-box,以免svg移动后手指点击不到触发不了事件 96 | 97 | 98 | ### 捏放事件 99 | 100 | ``` javaScript 101 | svgHam.on('pinchstart pinchmove', (e) => { 102 | let { scale, maxScale, minScale } = transform 103 | scale *= e.scale 104 | scale = scale >= maxScale ? maxScale : scale 105 | scale = scale <= minScale ? minScale : scale 106 | transform.scale = scale 107 | svgBox.style.transform = `scale(${scale})` 108 | }) 109 | ``` 110 | 设置maxScale,minScale以免无限大或者无限小 111 | 112 | **· 注意这里缩放的是box而不是svg本身,否则拖动后再缩放就会发现整个SVG都跑偏了** 113 | 114 | ### 拖动事件 115 | 116 | 117 | ``` 118 | function checkXY(x, y) { 119 | let { minX, minY, maxX, maxY } = transform 120 | x = x > maxX ? maxX : x 121 | x = x < minX ? minX : x 122 | y = y > maxY ? maxY : y 123 | y = y < minY ? minY : y 124 | return { 125 | x, 126 | y 127 | } 128 | } 129 | 130 | svgHam.on('panstart panmove', (e) => { 131 | let { scale, translateX, translateY, svgScale } = transform 132 | let y = translateY + e.deltaY / scale 133 | let x = translateX + e.deltaX / scale 134 | let validXY = checkXY(x, y) 135 | svgTarget.style.transform = `translate(${validXY.x}px, ${validXY.y}px) scale(${svgScale})` 136 | }) 137 | 138 | svgHam.on('panend', (e) => { 139 | let { scale, translateX, translateY} = transform 140 | let y = translateY + e.deltaY / scale 141 | let x = translateX + e.deltaX / scale 142 | let validXY = checkXY(x, y) 143 | transform.translateY = validXY.y 144 | transform.translateX = validXY.x 145 | }) 146 | ``` 147 | 偏移结束后更新偏移值,因为hammer提供的event事件的偏差值deltaY/deltaX是你手指点击初始位置以及移动后的差值。 148 | 149 | 偏移量 / scale 可以有效的控制放大后的拖动速度,否则放大后一拖动,整个SVG就跑走了。 150 | 151 | ### 选座 152 | 做好了上述效果,选座就简单了 直接一个点击事件就好了 153 | 154 | 155 | ``` javascript 156 | document.querySelector('.svg-box').addEventListener('click', selectSeat) 157 | // svg 点击事件 选座 158 | function selectSeat (e) { 159 | if (e.target.tagName !== 'circle') return false 160 | e.target.style.fill = e.target.style.fill === 'red' ? '#ccc' : 'red' // 选中的座位变成红色 161 | // do something... 162 | } 163 | ``` 164 | 165 | ### [完整代码](https://github.com/Hecoffee/TicketMap) 166 | ------------ 167 | 168 | > 写在最后 169 | 170 | 不是十分完美,不过也满足现在的需求,希望能给大家带来一点启发。不足的地方,也望各位大神指点迷津。 171 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | TicketMap 7 | 28 | 29 | 30 | 31 |
32 |
33 | 34 |
35 |
36 | 123 | 124 | 125 | --------------------------------------------------------------------------------