half-screen.uvue 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. <template>
  2. <view ref="page" class="page">
  3. <text class="tip">半屏弹窗,演示了弹出层内scroll-view滚动到顶时由滚变拖。本效果是通过监听TouchEvent实现,当半屏窗口移动时禁用scroll-view的滚动,避免两者的冲突。</text>
  4. <button class="bottomButton" @click="switchHalfScreen(true)">打开弹窗</button>
  5. <view ref="halfScreen" class="halfScreen" @touchstart="onHalfTouchStart" @touchmove="onHalfTouchMove"
  6. @touchend="onHalfTouchEnd">
  7. <view class="halfTitle">半屏弹窗标题</view>
  8. <scroll-view ref="halfScroll" class="halfScroll" bounces="true" :direction="scrollDirection">
  9. <view v-for="(item,index) in list" :key="index" class="item">
  10. half screen content-{{item}}
  11. </view>
  12. </scroll-view>
  13. </view>
  14. </view>
  15. </template>
  16. <script>
  17. export default {
  18. data() {
  19. return {
  20. list: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15'],
  21. totalHeight: 0, //总高度
  22. halfMove: false, //是否Move,响应TouchMove
  23. halfScreenY: 0, //响应TouchMove的起始点Y坐标
  24. halfOffset: 0, //偏移的位置,translateY
  25. halfHeight: 0, //高度
  26. lastY: 0, //上次
  27. lastY2: 0, //
  28. bAnimation: false, //是否动画
  29. halfNode: null as UniElement | null,
  30. scrollNode: null as UniElement | null,
  31. scrollDirection: 'vertical'
  32. }
  33. },
  34. methods: {
  35. onHalfTouchStart(_ : TouchEvent) {
  36. this.halfNode?.style?.setProperty('transition-duration', 0);
  37. },
  38. onHalfTouchMove(e : TouchEvent) {
  39. if (this.bAnimation) {//容错处理
  40. return;
  41. }
  42. let top : number = this.scrollNode?.scrollTop ?? 0;
  43. let p = e.touches[0];
  44. this.lastY2 = this.lastY;
  45. this.lastY = p.screenY;
  46. if (top <= 0.01 || this.halfMove) {
  47. if (this.halfScreenY == 0) {
  48. this.halfScreenY = p.screenY;
  49. }
  50. let offset = p.screenY - this.halfScreenY;
  51. if (offset > 0) {//向下滚动
  52. this.halfMove = true;
  53. // #ifdef APP
  54. //this.scrollNode?.setAttribute('scroll-y', 'false');
  55. this.scrollNode?.setAttribute('direction', 'none');
  56. // #endif
  57. // #ifdef WEB || MP
  58. this.scrollDirection = 'none';
  59. // #endif
  60. this.halfNode?.style?.setProperty('transform', 'translateY(' + offset.toFixed(2) + 'px)');
  61. this.halfOffset = offset;
  62. } else if (this.halfOffset > 0) {//向上滚动
  63. offset = this.halfScreenY - p.screenY;
  64. if (offset > this.halfOffset) {
  65. offset = 0;
  66. this.halfMove = false;
  67. }
  68. // #ifdef APP
  69. //this.scrollNode?.setAttribute('scroll-y', 'true');
  70. this.scrollNode?.setAttribute('direction', 'vertical');
  71. // #endif
  72. // #ifdef WEB || MP
  73. this.scrollDirection = 'vertical';
  74. // #endif
  75. this.halfNode?.style?.setProperty('transform', 'translateY(' + offset.toFixed(2) + 'px)');
  76. this.halfOffset = offset;
  77. }
  78. }
  79. // #ifdef WEB
  80. e.preventDefault();
  81. // #endif
  82. },
  83. onHalfTouchEnd(_ : TouchEvent) {
  84. this.halfScreenY = 0;
  85. if (this.bAnimation) {//容错处理
  86. return;
  87. }
  88. let top : number = this.scrollNode?.scrollTop ?? 0;
  89. let bHide = (this.halfHeight - this.halfOffset) < this.halfHeight / 4;
  90. if (bHide) {
  91. bHide = this.lastY2 > 0 && this.lastY2 <= this.lastY;
  92. } else if (top <= 0.01) {
  93. bHide = (this.lastY - this.lastY2) > 3; //向下滑动计算加速度判断是否关闭,简单处理未考虑时间
  94. }
  95. if (bHide) {
  96. this.switchHalfScreen(false);
  97. } else if (this.halfOffset > 0) {
  98. this.resumeHalfScreen();
  99. }
  100. },
  101. switchHalfScreen(show : boolean) {
  102. if (show && ('visible' == this.halfNode?.style?.getPropertyValue('visibility'))) {//容错处理
  103. console.log('quick click button!!!');
  104. return;
  105. }
  106. this.halfMove = false;
  107. // #ifdef APP
  108. //this.scrollNode?.setAttribute('scroll-y', 'true');
  109. this.scrollNode?.setAttribute('direction', 'vertical');
  110. // #endif
  111. // #ifdef WEB || MP
  112. this.scrollDirection = 'vertical';
  113. // #endif
  114. this.halfScreenY = 0;
  115. this.halfOffset = 0;
  116. let top = this.totalHeight;
  117. let time = 300;
  118. if (show) {
  119. top = this.totalHeight * 30 / 100; //计算显示的位置
  120. this.halfNode?.style?.setProperty('visibility', 'visible');
  121. this.halfNode?.style?.setProperty('transition-timing-function', 'ease-in-out');
  122. } else {
  123. this.halfNode?.style?.setProperty('transition-timing-function', 'linear');
  124. time *= (this.halfHeight / this.totalHeight); //计算关闭动画时间
  125. }
  126. this.halfNode?.style?.setProperty('transition-duration', time.toFixed(0)+'ms');
  127. this.halfNode?.style?.setProperty('transition-property', 'top');
  128. this.halfNode?.style?.setProperty('top', top.toFixed(2)+'px');
  129. setTimeout(() => {
  130. if (!show) {
  131. this.halfNode?.style?.setProperty('visibility', 'hidden');
  132. this.halfNode?.style?.setProperty('transition-duration', 0);
  133. this.halfNode?.style?.setProperty('transform', '');
  134. }
  135. this.halfNode?.style?.setProperty('transition-property', 'none');
  136. this.bAnimation = false;
  137. }, time)
  138. this.bAnimation = true;
  139. },
  140. resumeHalfScreen() {
  141. let time = 300;//(500*this.halfOffset/this.halfHeight).toFixed(0); //回弹动画时间
  142. this.halfNode?.style?.setProperty('transition-duration', time.toFixed(0)+'ms');
  143. this.halfNode?.style?.setProperty('transition-timing-function', 'ease-in-out');
  144. this.halfNode?.style?.setProperty('transition-property', 'transform');
  145. this.halfNode?.style?.setProperty('transform', 'translateY(0px)');
  146. this.halfMove = false;
  147. // #ifdef APP
  148. //this.scrollNode?.setAttribute('scroll-y', 'true');
  149. this.scrollNode?.setAttribute('direction', 'vertical');
  150. // #endif
  151. // #ifdef WEB || MP
  152. this.scrollDirection = 'vertical';
  153. // #endif
  154. this.halfScreenY = 0;
  155. this.halfOffset = 0;
  156. setTimeout(() => {
  157. this.bAnimation = false;
  158. this.halfNode?.style?.setProperty('transition-property', 'none');
  159. }, time)
  160. this.bAnimation = true;
  161. }
  162. },
  163. onReady() {
  164. this.halfNode = this.$refs['halfScreen'] as UniElement;//uni.getElementById('halfScreen');
  165. this.scrollNode = this.$refs['halfScroll'] as UniElement;//uni.getElementById('halfScroll');
  166. this.halfNode!.getBoundingClientRectAsync()!.then((rect: DOMRect) => {
  167. this.halfHeight = rect.height
  168. });
  169. (this.$refs['page'] as UniElement).getBoundingClientRectAsync()!.then((rect: DOMRect) => {
  170. this.totalHeight = rect.height
  171. this.halfNode?.style?.setProperty('top', this.totalHeight.toFixed(2)+'px');
  172. });
  173. },
  174. onResize() {
  175. this.halfNode?.getBoundingClientRectAsync()!.then((rect: DOMRect) => {
  176. this.halfHeight = rect.height
  177. });
  178. this.totalHeight = uni.getWindowInfo().windowHeight;
  179. this.halfNode?.style?.setProperty('top', this.totalHeight.toFixed(2)+'px');
  180. this.halfNode?.style?.setProperty('visibility', 'hidden');
  181. },
  182. onBackPress(): boolean {
  183. // #ifndef MP
  184. if('visible' == this.halfNode?.style?.getPropertyValue('visibility')){
  185. this.switchHalfScreen(false);
  186. return true;
  187. }
  188. return false;
  189. // #endif
  190. }
  191. }
  192. </script>
  193. <style>
  194. /* #ifdef MP */
  195. page {
  196. overflow: hidden
  197. }
  198. /* #endif */
  199. .page {
  200. flex: 1;
  201. background-color: darkgrey;
  202. }
  203. .tip {
  204. margin: 10px
  205. }
  206. .bottomButton {
  207. position: absolute;
  208. width: 100%;
  209. bottom: 0px;
  210. /* #ifdef APP */
  211. padding-bottom: env(safe-area-inset-bottom, 0px);
  212. /* #endif */
  213. }
  214. .halfScreen {
  215. position: absolute;
  216. top: 100%;
  217. width: 100%;
  218. height: 70%;
  219. transition-timing-function: ease-in-out;
  220. /*ease ease-in ease-out ease-in-out linear step-start step-end*/
  221. transition-property: top;
  222. transition-duration: 0ms;
  223. visibility: hidden;
  224. }
  225. .halfTitle {
  226. align-items: center;
  227. justify-content: center;
  228. height: 48px;
  229. background-color: ghostwhite;
  230. border-radius: 10px 10px 0 0;
  231. }
  232. .halfScroll {
  233. background-color: white;
  234. flex: 1;
  235. }
  236. .item {
  237. height: 100px;
  238. }
  239. </style>