card.uvue 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. <template>
  2. <view class="card" ref="card" @touchstart="touchstart($event as TouchEvent)"
  3. @touchmove="touchmove($event as TouchEvent)" @touchend="touchend" @touchcancel="touchend">
  4. <image class="card-img" ref="card-img" :src="img"></image>
  5. <view class="state">
  6. <image class="state-icon like" ref="state-icon-like" src="/static/template/drop-card/like.png" mode="widthFix">
  7. </image>
  8. <image class="state-icon dislike" ref="state-icon-dislike" src="/static/template/drop-card/dislike.png"
  9. mode="widthFix"></image>
  10. <!-- cardIndex:{{cardIndex}} -->
  11. </view>
  12. </view>
  13. </template>
  14. <script>
  15. let sX : number = 0,
  16. sY : number = 0,
  17. screenWidth : number = 1,
  18. screenHeight : number = 1,
  19. floating : boolean = false,
  20. touchstartAfter : boolean = false;
  21. export default {
  22. options: {
  23. virtualHost: true
  24. },
  25. data() {
  26. return {
  27. $elementMap: new Map<string, UniElement>(),
  28. x: 0 as number,
  29. y: 0 as number,
  30. // 飘走的卡片计数
  31. floatCount: 0 as number
  32. }
  33. },
  34. props: {
  35. img: {
  36. type: String,
  37. default: "https://web-ext-storage.dcloud.net.cn/hello-uni-app-x/drop-card-1.jpg"
  38. },
  39. cardIndex: {
  40. type: Number,
  41. default: 0
  42. }
  43. },
  44. computed: {
  45. movePercent() : number {
  46. return Math.abs(this.x) / (screenWidth / 2 * 3)
  47. },
  48. likeOpacity() : number {
  49. return this.x < 0 ? 0 : this.movePercent * 100
  50. },
  51. dislikeOpacity() : number {
  52. return this.x > 0 ? 0 : this.movePercent * 100
  53. }
  54. },
  55. mounted() {
  56. screenWidth = uni.getWindowInfo().screenWidth
  57. screenHeight = uni.getWindowInfo().screenHeight;
  58. // TODO 需要延迟设置才能生效
  59. setTimeout(() => {
  60. this.setElementStyle('card', 'height', screenHeight * 0.7 + 'px');
  61. this.setElementStyle('card-img', 'height', screenHeight * 0.7 + 'px');
  62. this.initCardStyle()
  63. }, 200)
  64. uni.$on('uni-drop-card-float', () => {
  65. this.floatCount++
  66. this.initCardStyle()
  67. })
  68. },
  69. unmounted() {
  70. uni.$off('uni-drop-card-float')
  71. },
  72. methods: {
  73. initCardStyle() {
  74. let _index = (this.cardIndex + this.floatCount) % 3
  75. // console.log('~~~~~~_index:'+_index + ' cardIndex:'+this.cardIndex+' floatCount:'+this.floatCount);
  76. this.setElementStyle('card', 'z-index', _index)
  77. this.setElementStyle('card', 'margin-top', screenHeight * 0.15 - 30 * _index + 'px');
  78. this.setElementStyle('card', 'transform', 'scale(' + (0.9 + 0.05 * _index) + ')')
  79. },
  80. // 工具方法,用于快速设置 Element 的 style
  81. setElementStyle(refName : string, propertyName : string, propertyStyle : any) : void {
  82. const elementMap = this.$data['$elementMap'] as Map<string, UniElement>
  83. let element : UniElement | null = elementMap.get(refName)
  84. if (element == null) {
  85. element = this.$refs[refName] as UniElement;
  86. elementMap.set(refName, element)
  87. } else {
  88. // console.log('直接拿');
  89. }
  90. element.style.setProperty(propertyName, propertyStyle);
  91. },
  92. touchstart(e : TouchEvent) {
  93. // console.log('touchstart')
  94. if (floating) {
  95. return // 浮动动画进行中
  96. }
  97. sX = e.touches[0].screenX;
  98. sY = e.touches[0].screenY;
  99. this.x = 0
  100. this.y = 0
  101. touchstartAfter = true
  102. },
  103. touchmove(e : TouchEvent) {
  104. // console.log('touchmove')
  105. if (!touchstartAfter || floating) {
  106. return // floating:浮动动画进行中
  107. }
  108. this.x += e.touches[0].screenX - sX;
  109. this.y += e.touches[0].screenY - sY;
  110. sX = e.touches[0].screenX;
  111. sY = e.touches[0].screenY;
  112. this.moveCard()
  113. // #ifdef WEB
  114. //阻止默认行为
  115. e.preventDefault()
  116. // #endif
  117. },
  118. touchend() {
  119. // console.log('touchend')
  120. touchstartAfter = false
  121. if (floating) {
  122. return // 浮动动画进行中
  123. }
  124. floating = true
  125. // 设置释放之后飘走的方向 0回到坐标中心 1向右 2向左
  126. let k : number = 0;
  127. if (this.x > screenWidth / 10) {
  128. k = 1
  129. } else if (this.x < screenWidth * -1 / 10) {
  130. k = -1
  131. }
  132. const _this = this
  133. function cardTo(x : number, y : number, callback : () => void, speed : number = 10) {
  134. let interval : number = 0
  135. let acceleration : number = 1
  136. interval = setInterval(() => {
  137. // 加速度
  138. acceleration += 0.2
  139. const dx = x - _this.x
  140. if (Math.abs(dx) < 1) {
  141. _this.x = x
  142. } else {
  143. _this.x += dx / speed * acceleration
  144. }
  145. const dy = y - _this.y
  146. if (Math.abs(dy) < 1) {
  147. _this.y = y
  148. } else {
  149. _this.y += dy / speed * acceleration
  150. }
  151. _this.moveCard()
  152. if (_this.x == x && _this.y == y) {
  153. clearInterval(interval)
  154. callback()
  155. }
  156. }, 16)
  157. }
  158. if (Math.floor(k) != 0) {
  159. cardTo(k * screenWidth * 1.3, this.y * 3, () => {
  160. // 状态图标变回透明
  161. this.setElementStyle("state-icon-like", 'opacity', 0)
  162. this.setElementStyle("state-icon-dislike", 'opacity', 0)
  163. // 设置为透明,防止飘回时因为 margin-top 太高,露出来
  164. this.setElementStyle("card", 'opacity', 0)
  165. setTimeout(() => {
  166. this.setElementStyle("card", 'opacity', 1)
  167. }, 300)
  168. // 执行卡片飘动后事件,注意uni.$emit是全局事件。其他卡片也会执行
  169. uni.$emit('uni-drop-card-float', null)
  170. floating = false
  171. }, 8)
  172. } else {
  173. const _x : number = this.x
  174. const _y : number = this.y
  175. cardTo(Math.floor(_x * -0.05), Math.floor(_y * -0.05), () => {
  176. cardTo(0, 0, () => {
  177. console.log('bounce')
  178. floating = false
  179. }, 30)
  180. })
  181. }
  182. },
  183. moveCard() {
  184. this.setElementStyle("card",
  185. 'transform',
  186. `translate(${this.x}px,${this.y}px) rotate(${this.x / -30}deg) scale(1)`
  187. )
  188. this.setElementStyle("state-icon-like", 'opacity', this.x < 0 ? 0 : this.movePercent * 10)
  189. this.setElementStyle("state-icon-dislike", 'opacity', this.x > 0 ? 0 : this.movePercent * 10)
  190. }
  191. }
  192. }
  193. </script>
  194. <style>
  195. .card {
  196. width:95%;
  197. height: 0px;
  198. position: absolute;
  199. top: 0px;
  200. /* left: 0px; */
  201. margin: 0 12px;
  202. margin-top: 50px;
  203. border-radius: 10px;
  204. box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
  205. background-color: #FFF;
  206. transition: margin-top 300ms;
  207. transition-timing-function: ease-in;
  208. }
  209. .card-img {
  210. width: 100%;
  211. height: 0px;
  212. border-radius: 10px;
  213. }
  214. .state {
  215. top: 10px;
  216. left: 10px;
  217. width: 86%;
  218. padding: 4px;
  219. position: absolute;
  220. flex-direction: row;
  221. justify-content: space-between;
  222. }
  223. .state-icon {
  224. width: 30px;
  225. height: 30px;
  226. border: 1px solid #FFF;
  227. background-color: #FFF;
  228. padding: 3px;
  229. border-radius: 100px;
  230. box-shadow: 0 0 1px #EBEBEB;
  231. opacity: 0;
  232. }
  233. </style>