選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

uni-transition.vue 6.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. <template>
  2. <view v-if="isShow" ref="ani" :animation="animationData" :class="customClass" :style="transformStyles" @click="onClick"><slot></slot></view>
  3. </template>
  4. <script>
  5. import { createAnimation } from './createAnimation'
  6. /**
  7. * Transition 过渡动画
  8. * @description 简单过渡动画组件
  9. * @tutorial https://ext.dcloud.net.cn/plugin?id=985
  10. * @property {Boolean} show = [false|true] 控制组件显示或隐藏
  11. * @property {Array|String} modeClass = [fade|slide-top|slide-right|slide-bottom|slide-left|zoom-in|zoom-out] 过渡动画类型
  12. * @value fade 渐隐渐出过渡
  13. * @value slide-top 由上至下过渡
  14. * @value slide-right 由右至左过渡
  15. * @value slide-bottom 由下至上过渡
  16. * @value slide-left 由左至右过渡
  17. * @value zoom-in 由小到大过渡
  18. * @value zoom-out 由大到小过渡
  19. * @property {Number} duration 过渡动画持续时间
  20. * @property {Object} styles 组件样式,同 css 样式,注意带’-‘连接符的属性需要使用小驼峰写法如:`backgroundColor:red`
  21. */
  22. export default {
  23. name: 'uniTransition',
  24. emits:['click','change'],
  25. props: {
  26. show: {
  27. type: Boolean,
  28. default: false
  29. },
  30. modeClass: {
  31. type: [Array, String],
  32. default() {
  33. return 'fade'
  34. }
  35. },
  36. duration: {
  37. type: Number,
  38. default: 300
  39. },
  40. styles: {
  41. type: Object,
  42. default() {
  43. return {}
  44. }
  45. },
  46. customClass:{
  47. type: String,
  48. default: ''
  49. }
  50. },
  51. data() {
  52. return {
  53. isShow: false,
  54. transform: '',
  55. opacity: 1,
  56. animationData: {},
  57. durationTime: 300,
  58. config: {}
  59. }
  60. },
  61. watch: {
  62. show: {
  63. handler(newVal) {
  64. if (newVal) {
  65. this.open()
  66. } else {
  67. // 避免上来就执行 close,导致动画错乱
  68. if (this.isShow) {
  69. this.close()
  70. }
  71. }
  72. },
  73. immediate: true
  74. }
  75. },
  76. computed: {
  77. // 生成样式数据
  78. stylesObject() {
  79. let styles = {
  80. ...this.styles,
  81. 'transition-duration': this.duration / 1000 + 's'
  82. }
  83. let transform = ''
  84. for (let i in styles) {
  85. let line = this.toLine(i)
  86. transform += line + ':' + styles[i] + ';'
  87. }
  88. return transform
  89. },
  90. // 初始化动画条件
  91. transformStyles() {
  92. return 'transform:' + this.transform + ';' + 'opacity:' + this.opacity + ';' + this.stylesObject
  93. }
  94. },
  95. created() {
  96. // 动画默认配置
  97. this.config = {
  98. duration: this.duration,
  99. timingFunction: 'ease',
  100. transformOrigin: '50% 50%',
  101. delay: 0
  102. }
  103. this.durationTime = this.duration
  104. },
  105. methods: {
  106. /**
  107. * ref 触发 初始化动画
  108. */
  109. init(obj = {}) {
  110. if (obj.duration) {
  111. this.durationTime = obj.duration
  112. }
  113. this.animation = createAnimation(Object.assign(this.config, obj),this)
  114. },
  115. /**
  116. * 点击组件触发回调
  117. */
  118. onClick() {
  119. this.$emit('click', {
  120. detail: this.isShow
  121. })
  122. },
  123. /**
  124. * ref 触发 动画分组
  125. * @param {Object} obj
  126. */
  127. step(obj, config = {}) {
  128. if (!this.animation) return
  129. for (let i in obj) {
  130. try {
  131. if(typeof obj[i] === 'object'){
  132. this.animation[i](...obj[i])
  133. }else{
  134. this.animation[i](obj[i])
  135. }
  136. } catch (e) {
  137. console.log('输出内容',e)
  138. console.error(`方法 ${i} 不存在`)
  139. }
  140. }
  141. this.animation.step(config)
  142. return this
  143. },
  144. /**
  145. * ref 触发 执行动画
  146. */
  147. run(fn) {
  148. if (!this.animation) return
  149. this.animation.run(fn)
  150. },
  151. // 开始过度动画
  152. open() {
  153. clearTimeout(this.timer)
  154. this.transform = ''
  155. this.isShow = true
  156. let { opacity, transform } = this.styleInit(false)
  157. if (typeof opacity !== 'undefined') {
  158. this.opacity = opacity
  159. }
  160. this.transform = transform
  161. // 确保动态样式已经生效后,执行动画,如果不加 nextTick ,会导致 wx 动画执行异常
  162. this.$nextTick(() => {
  163. // TODO 定时器保证动画完全执行,目前有些问题,后面会取消定时器
  164. this.timer = setTimeout(() => {
  165. this.animation = createAnimation(this.config, this)
  166. this.tranfromInit(false).step()
  167. this.animation.run()
  168. this.$emit('change', {
  169. detail: this.isShow
  170. })
  171. }, 20)
  172. })
  173. },
  174. // 关闭过度动画
  175. close(type) {
  176. if (!this.animation) return
  177. this.tranfromInit(true)
  178. .step()
  179. .run(() => {
  180. this.isShow = false
  181. this.animationData = null
  182. this.animation = null
  183. let { opacity, transform } = this.styleInit(false)
  184. this.opacity = opacity || 1
  185. this.transform = transform
  186. this.$emit('change', {
  187. detail: this.isShow
  188. })
  189. })
  190. },
  191. // 处理动画开始前的默认样式
  192. styleInit(type) {
  193. let styles = {
  194. transform: ''
  195. }
  196. let buildStyle = (type, mode) => {
  197. if (mode === 'fade') {
  198. styles.opacity = this.animationType(type)[mode]
  199. } else {
  200. styles.transform += this.animationType(type)[mode] + ' '
  201. }
  202. }
  203. if (typeof this.modeClass === 'string') {
  204. buildStyle(type, this.modeClass)
  205. } else {
  206. this.modeClass.forEach(mode => {
  207. buildStyle(type, mode)
  208. })
  209. }
  210. return styles
  211. },
  212. // 处理内置组合动画
  213. tranfromInit(type) {
  214. let buildTranfrom = (type, mode) => {
  215. let aniNum = null
  216. if (mode === 'fade') {
  217. aniNum = type ? 0 : 1
  218. } else {
  219. aniNum = type ? '-100%' : '0'
  220. if (mode === 'zoom-in') {
  221. aniNum = type ? 0.8 : 1
  222. }
  223. if (mode === 'zoom-out') {
  224. aniNum = type ? 1.2 : 1
  225. }
  226. if (mode === 'slide-right') {
  227. aniNum = type ? '100%' : '0'
  228. }
  229. if (mode === 'slide-bottom') {
  230. aniNum = type ? '100%' : '0'
  231. }
  232. }
  233. this.animation[this.animationMode()[mode]](aniNum)
  234. }
  235. if (typeof this.modeClass === 'string') {
  236. buildTranfrom(type, this.modeClass)
  237. } else {
  238. this.modeClass.forEach(mode => {
  239. buildTranfrom(type, mode)
  240. })
  241. }
  242. return this.animation
  243. },
  244. animationType(type) {
  245. return {
  246. fade: type ? 1 : 0,
  247. 'slide-top': `translateY(${type ? '0' : '-100%'})`,
  248. 'slide-right': `translateX(${type ? '0' : '100%'})`,
  249. 'slide-bottom': `translateY(${type ? '0' : '100%'})`,
  250. 'slide-left': `translateX(${type ? '0' : '-100%'})`,
  251. 'zoom-in': `scaleX(${type ? 1 : 0.8}) scaleY(${type ? 1 : 0.8})`,
  252. 'zoom-out': `scaleX(${type ? 1 : 1.2}) scaleY(${type ? 1 : 1.2})`
  253. }
  254. },
  255. // 内置动画类型与实际动画对应字典
  256. animationMode() {
  257. return {
  258. fade: 'opacity',
  259. 'slide-top': 'translateY',
  260. 'slide-right': 'translateX',
  261. 'slide-bottom': 'translateY',
  262. 'slide-left': 'translateX',
  263. 'zoom-in': 'scale',
  264. 'zoom-out': 'scale'
  265. }
  266. },
  267. // 驼峰转中横线
  268. toLine(name) {
  269. return name.replace(/([A-Z])/g, '-$1').toLowerCase()
  270. }
  271. }
  272. }
  273. </script>
  274. <style></style>