rive-ani.vue 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. <template>
  2. <view class="rive-ani" id="rive-ani" v-if="state.comVisable" >
  3. <view class="coin-max-tip" v-if="state.maxTipVisible" :style="{backgroundImage: `url(${staticImg.blockRect})`, top: '3vw'}" >
  4. 已达上限
  5. </view>
  6. <view class="coin-total" :style="{top: '3vw' }" >
  7. <span>{{wisdomCoinStore.total || 0}}</span>
  8. <view class="coin-logo" id="coin-logo" :animation="state.heartbeatAni" >
  9. <img :src="staticImg.coinTotal" alt="">
  10. </view>
  11. </view>
  12. <view class="coin-ani" id="coin-ani" v-if="state.visible" :key="state.aniKey" >
  13. <view
  14. class="coin-item"
  15. v-for="item in state.cointCount"
  16. :key="item"
  17. :animation="state.animation[item - 1]"
  18. :id="`coin-item-${item}`"
  19. >
  20. <img :src="staticImg.coinAni" alt="" >
  21. </view>
  22. <view class="coin-count" id="coin-count" >+ {{state.cointCount}}</view>
  23. </view>
  24. </view>
  25. </template>
  26. <script setup lang="ts" name="rive " >
  27. import { onMounted, ref, reactive, getCurrentInstance, defineExpose, nextTick} from 'vue'
  28. import { Rive } from "@rive-app/canvas"
  29. import { useStaticImg, useQueryElInfo, useSchedulerOnce, useNavbarInfo } from '@/hooks/index'
  30. import { useWisdomCoinStore } from '@/store'
  31. import { AudioController } from '@/controller/AudioController';
  32. // import threeStarRiv from '@/ani/three_star.riv'
  33. enum AniEnum {
  34. 'one',
  35. 'two',
  36. 'three',
  37. 'four',
  38. 'five',
  39. 'six'
  40. }
  41. /**
  42. * 动画逻辑
  43. * 1. 得几颗星就控制rive动画给几颗星
  44. * 2. 然后rive动画渐隐
  45. * 3. 出现金币 +n 的字样
  46. * 4. 金币飞到右上角金币框
  47. */
  48. interface State {
  49. rive: Rive,
  50. animation: any,
  51. visible: boolean,
  52. heartbeatAni: any,
  53. cointCount: number,
  54. comVisable: boolean,
  55. cardType: 0 | 1,
  56. maxTipVisible: boolean
  57. aniKey: number
  58. }
  59. const state = reactive<Partial<State>>({
  60. animation: [],
  61. visible: false,
  62. heartbeatAni: null,
  63. cointCount: 0,
  64. comVisable: false,
  65. cardType: 0,
  66. maxTipVisible: false,
  67. aniKey: 0
  68. })
  69. const staticImg = useStaticImg()
  70. const wisdomCoinStore = useWisdomCoinStore()
  71. const navbarInfo = useNavbarInfo()
  72. const coinLoginRef = ref()
  73. let rive: Rive
  74. let _resolve: any
  75. const initHeartbeatAni = () => {
  76. var animation = uni.createAnimation({
  77. timingFunction: "linear",
  78. transformOrigin: "0% 0%"
  79. });
  80. return animation.scale(1.1, 1.1).step({duration: 100}).scale(1, 1).step({duration: 100}).export()
  81. }
  82. // 1. 初始化rive动画
  83. const initRive = () => {
  84. const canvas = document.createElement('canvas')
  85. // const scale = window.devicePixelRatio;
  86. const scale = 1;
  87. canvas.width = 375 * scale;
  88. canvas.height = 375 * scale;
  89. canvas.id = 'rive-canvas'
  90. canvas.style.position = 'absolute'
  91. document.getElementById('rive-ani')?.appendChild(canvas)
  92. rive = new Rive({
  93. // src: "https://res-game.luojigou.vip/coin1234.riv",
  94. src: 'https://res-game.luojigou.vip/three_star.riv',
  95. canvas: canvas,
  96. autoplay: false,
  97. stateMachines: "bumpy",
  98. // artboard: state.cardType === 0 ? 'si-xing' : 'six-xing',
  99. artboard: 'Three Star Board',
  100. onLoad: () => {
  101. rive.resizeToCanvas()
  102. console.log(rive);
  103. rive.play(AniEnum[state.cointCount! - 1])
  104. // rive.play('one')
  105. for (let i = 0; i < state.cointCount!; i++) {
  106. useSchedulerOnce( () => {
  107. AudioController.playCoinLight()
  108. }, 1200 + i * 100)
  109. }
  110. },
  111. onStop: () => {
  112. fadeOutRive()
  113. useSchedulerOnce(() => {
  114. state.visible = true
  115. useSchedulerOnce(() => document.getElementById('coin-count')!.style.display = 'none', 1200)
  116. useSchedulerOnce(() => goBezier(), 1400)
  117. }, 1000)
  118. }
  119. });
  120. }
  121. const createAnimationStep = (animation: UniApp.Animation, count: number) => {
  122. for (let i = 0; i < count; i++) {
  123. state.animation.push(animation.step({duration: 400 + i * 200}).export())
  124. useSchedulerOnce(() => {
  125. if (wisdomCoinStore.remainderWisdomCoin <= 0) {
  126. state.maxTipVisible = true
  127. } else {
  128. wisdomCoinStore.total ++
  129. wisdomCoinStore.remainderWisdomCoin--
  130. }
  131. document.getElementById(`coin-item-${i + 1}`)!.style.display = 'none'
  132. state.heartbeatAni = initHeartbeatAni()
  133. }, 400 + i * 200)
  134. }
  135. }
  136. // 2. rive动画渐隐消失
  137. const fadeOutRive = () => {
  138. const riveCanvas = document.getElementById('rive-canvas')!
  139. riveCanvas.style.opacity = '0'
  140. riveCanvas.style.transition = '1s'
  141. }
  142. const goBezier = () => {
  143. var animation = uni.createAnimation({
  144. timingFunction: "linear",
  145. transformOrigin: "0% 0%"
  146. });
  147. animation
  148. .top(coinLoginRef.value.top )
  149. .left(coinLoginRef.value.left)
  150. .scale(0.25, 0.25)
  151. createAnimationStep(animation, state.cointCount!)
  152. useSchedulerOnce(() => {
  153. _resolve()
  154. state.comVisable = false
  155. state.visible = false
  156. state.animation = []
  157. document.getElementById('coin-count')!.style.display = 'block'
  158. }, 1200 + state.cointCount! * 600)
  159. }
  160. const start = async (starCount: number, cardType: 0 | 1) => {
  161. state.comVisable = true
  162. state.cointCount = starCount
  163. state.cardType = cardType
  164. state.aniKey = Math.random()
  165. if (cardType === 0) {
  166. starCount <= 2 ? AudioController.playFail() : AudioController.playPass()
  167. } else {
  168. starCount <= 3 ? AudioController.playFail() : AudioController.playPass()
  169. }
  170. useSchedulerOnce(() => {
  171. useSchedulerOnce(initRive)
  172. }, 200)
  173. if (wisdomCoinStore.remainderWisdomCoin == 0) {
  174. state.maxTipVisible = true
  175. }
  176. nextTick(() => useQueryElInfo('#coin-logo', (res) => coinLoginRef.value = res, getCurrentInstance()!, false))
  177. return new Promise((resolve) => {
  178. _resolve = resolve
  179. })
  180. }
  181. defineExpose({
  182. start: start
  183. })
  184. onMounted(() => {
  185. })
  186. </script>
  187. <style lang="less" scoped >
  188. .rive-ani {
  189. width: 100vw;
  190. height: 100vh;
  191. position: fixed;
  192. top: 0;
  193. left: 0;
  194. z-index: 200;
  195. background-color: rgba(0,0,0, .6);
  196. display: flex;
  197. align-items: center;
  198. justify-content: center;
  199. .coin-max-tip {
  200. width: 88px;
  201. height: 33px;
  202. background-size: 100%;
  203. position: absolute;
  204. right: 120px;
  205. display: flex;
  206. align-items: center;
  207. background-repeat: no-repeat;
  208. font-size: 14px;
  209. font-family: PingFangSC-Semibold, PingFang SC;
  210. font-weight: 600;
  211. color: #FEFBFB;
  212. display: flex;
  213. justify-content: center;
  214. align-items: center;
  215. margin-top: 2px;
  216. }
  217. .coin-total {
  218. height: 36px;
  219. background: #414141;
  220. border-radius: 19px;
  221. position: absolute;
  222. right: 2vw;
  223. display: flex;
  224. align-items: center;
  225. padding: 0 8px;
  226. box-sizing: border-box;
  227. .coin-logo {
  228. margin-left: 6px;
  229. img {
  230. width: 20px;
  231. height: 20px;
  232. display: block;
  233. }
  234. }
  235. span {
  236. font-size: 18px;
  237. font-family: PingFangSC-Semibold, PingFang SC;
  238. font-weight: 600;
  239. color: #FEFBFB;
  240. display: block;
  241. }
  242. }
  243. .coin-ani {
  244. width: 100vw;
  245. height: 100vh;
  246. position: absolute;
  247. top: 0%;
  248. left: 0%;
  249. display: flex;
  250. align-items: center;
  251. justify-content: center;
  252. .coin-item {
  253. position: absolute;
  254. top: 50%;
  255. left: 50%;
  256. transform: translate(-50%, -50%);
  257. animation: jelly 0.5s;
  258. transform-origin: top center;
  259. }
  260. @keyframes jelly {
  261. 0% {
  262. scale: 0;
  263. }
  264. 100% {
  265. scale: 1;
  266. }
  267. }
  268. img {
  269. width: 30%;
  270. height: 30%;;
  271. }
  272. .coin-count {
  273. font-size: 200%;
  274. font-family: PingFangSC-Semibold, PingFang SC;
  275. font-weight: 600;
  276. color: #FF8024;
  277. // margin-left: 250rpx;
  278. // margin-bottom: 20rpx;
  279. }
  280. }
  281. }
  282. .uni-canvas-canvas {
  283. width: 1000px !important;
  284. }
  285. </style>