diff --git a/packages/taro-cli/templates/wxcloud/index.js b/packages/taro-cli/templates/wxcloud/index.js index a6eff62324e4..d3b9c233cc84 100644 --- a/packages/taro-cli/templates/wxcloud/index.js +++ b/packages/taro-cli/templates/wxcloud/index.js @@ -103,7 +103,7 @@ module.exports = function (creater, params, helper, cb) { typescript: true }) } else { - creater.template(template, path.join(clientDirName, 'pagejs'), path.join(sourceDir, 'pages', 'index', 'index.weapp.js'), { + creater.template(template, path.join(clientDirName, 'pagejs'), path.join(sourceDir, 'pages', 'index', 'index.js'), { css: currentStyleExt }) creater.template(template, path.join(clientDirName, 'components', 'login', 'index'), path.join(sourceDir, 'components', 'login', 'index.weapp.js'), { diff --git a/packages/taro-components/src/components/canvas/index.js b/packages/taro-components/src/components/canvas/index.js index 858497719548..ff1574466215 100644 --- a/packages/taro-components/src/components/canvas/index.js +++ b/packages/taro-components/src/components/canvas/index.js @@ -5,22 +5,30 @@ import classnames from 'classnames' import './style/index.css' -// canvas-id String canvas 组件的唯一标识符 -// disable-scroll Boolean false 当在 canvas 中移动时且有绑定手势事件时,禁止屏幕滚动以及下拉刷新 -// bindtouchstart EventHandle 手指触摸动作开始 -// bindtouchmove EventHandle 手指触摸后移动 -// bindtouchend EventHandle 手指触摸动作结束 -// bindtouchcancel EventHandle 手指触摸动作被打断,如来电提醒,弹窗 -// bindlongtap EventHandle 手指长按 500ms 之后触发,触发了长按事件后进行移动不会触发屏幕的滚动 -// binderror EventHandle 当发生错误时触发 error 事件,detail = {errMsg: 'something wrong'} +/** + * Canvas组件参数 + * @typedef CanvasProps + * @param {String} [canvasId=canvas] 组件的唯一标识符 + * @param {Boolean} [disableScroll=false] 当在 canvas 中移动时且有绑定手势事件时,禁止屏幕滚动以及下拉刷新 + * @param {EventHandle} onTouchstart 手指触摸动作开始 + * @param {EventHandle} onTouchmove 手指触摸后移动 + * @param {EventHandle} onTouchend 手指触摸动作结束 + * @param {EventHandle} onTouchcancel 手指触摸动作被打断,如来电提醒,弹窗 + * @param {EventHandle} onLongtap 手指长按 500ms 之后触发,触发了长按事件后进行移动不会触发屏幕的滚动 + * @param {EventHandle} onError 当发生错误时触发 error 事件,detail = {errMsg: 'something wrong'} + */ @touchable() -export default class Canvas extends Taro.PureComponent { +class Canvas extends Taro.PureComponent { + /** @type {CanvasProps} */ static defaultProps = { canvasId: '', disableScroll: false, - bindError: null + onError: null } + + /** @type {CanvasProps} */ + props width = 300 height = 150 getWrapRef = ref => { @@ -40,8 +48,8 @@ export default class Canvas extends Taro.PureComponent { this.height = height } componentDidCatch (e) { - const bindError = this.props.bindError - bindError && bindError({ + const onError = this.props.onError + onError && onError({ errMsg: e.message }) } @@ -68,3 +76,5 @@ export default class Canvas extends Taro.PureComponent { ) } } + +export default Canvas diff --git a/packages/taro-components/src/components/navigator/index.js b/packages/taro-components/src/components/navigator/index.js index 47735241be33..8a00d10d721d 100644 --- a/packages/taro-components/src/components/navigator/index.js +++ b/packages/taro-components/src/components/navigator/index.js @@ -8,19 +8,21 @@ import './navigator.css' /* eslint-disable prefer-promise-reject-errors */ -/* - *target String self 在哪个目标上发生跳转,默认当前小程序,可选值self/miniProgram - *url String 当前小程序内的跳转链接 - *open-type String navigate 跳转方式 - *delta Number 当 open-type 为 'navigateBack' 时有效,表示回退的层数 - *app-id String 当target="miniProgram"时有效,要打开的小程序 appId - *path String 当target="miniProgram"时有效,打开的页面路径,如果为空则打开首页 - *extra-data Object 当target="miniProgram"时有效,需要传递给目标小程序的数据,目标小程序可在 App.onLaunch(),App.onShow() 中获取到这份数据。详情 - version version release 当target="miniProgram"时有效,要打开的小程序版本,有效值 develop(开发版),trial(体验版),release(正式版),仅在当前小程序为开发版或体验版时此参数有效;如果当前小程序是 *式版,则打开的小程序必定是正式版。 - *bindsuccess String 当target="miniProgram"时有效,跳转小程序成功 - *bindfail String 当target="miniProgram"时有效,跳转小程序失败 - *bindcomplete String 当target="miniProgram"时有效,跳转小程序完成 - *aria-label String 无障碍访问,(属性)元素的额外描述 +/** + * Navigator组件参数 + * @typedef NavigatorProps + * @property {String} appId 当target="miniProgram"时有效,要打开的小程序 appId + * @property {String} ariaLabel 无障碍访问,(属性)元素的额外描述 + * @property {Number} delta 当 openType 为 'navigateBack' 时有效,表示回退的层数 + * @property {Object} extraData 当target="miniProgram"时有效,需要传递给目标小程序的数据,目标小程序可在 App.onLaunch(),App.onShow() 中获取到这份数据。详情 + * @property {String} [openType=navigate] 跳转方式 + * @property {String} path 当target="miniProgram"时有效,打开的页面路径,如果为空则打开首页 + * @property {String} [target=self] 在哪个目标上发生跳转,默认当前小程序,可选值self/miniProgram + * @property {String} url 当前小程序内的跳转链接 + * @property {version} [version=release] 当target="miniProgram"时有效,要打开的小程序版本,有效值 develop(开发版),trial(体验版),release(正式版),仅在当前小程序为开发版或体验版时此参数有效;如果当前小程序是 *式版,则打开的小程序必定是正式版。 + * @property {String} onFail 当target="miniProgram"时有效,跳转小程序失败 + * @property {String} onComplete 当target="miniProgram"时有效,跳转小程序完成 + * @property {String} onSuccess 当target="miniProgram"时有效,跳转小程序成功 */ /** @@ -29,7 +31,14 @@ import './navigator.css' * https://developers.weixin.qq.com/miniprogram/dev/component/navigator.html **/ +@hoverable({ + hoverClass: 'navigator-hover', + hoverStopPropergation: false, + hoverStartTime: 50, + hoverStayTime: 600 +}) class Navigator extends Taro.Component { + /** @type {NavigatorProps} */ static defaultProps = { target: 'self', url: null, @@ -39,13 +48,15 @@ class Navigator extends Taro.Component { path: null, extraData: {}, version: 'release', - bindSuccess: null, - bindFail: null, - bindComplete: null, + onSuccess: null, + onFail: null, + onComplete: null, isHover: false } + /** @type {NavigationProps} */ + props onClick = () => { - const { openType, bindSuccess, bindFail, bindComplete } = this.props + const { openType, onSuccess, onFail, onComplete } = this.props let promise switch (openType) { case 'navigate': @@ -81,11 +92,11 @@ class Navigator extends Taro.Component { } if (promise) { promise.then(res => { - bindSuccess && bindSuccess(res) + onSuccess && onSuccess(res) }).catch(res => { - bindFail && bindFail(res) + onFail && onFail(res) }).finally(res => { - bindComplete && bindComplete(res) + onComplete && onComplete(res) }) } } @@ -105,9 +116,4 @@ class Navigator extends Taro.Component { } } -export default hoverable({ - hoverClass: 'navigator-hover', - hoverStopPropergation: false, - hoverStartTime: 50, - hoverStayTime: 600 -})(Navigator) +export default Navigator diff --git a/packages/taro-components/src/components/tabbar/index.js b/packages/taro-components/src/components/tabbar/index.js index 069b4433ed50..93735fd4ba5f 100644 --- a/packages/taro-components/src/components/tabbar/index.js +++ b/packages/taro-components/src/components/tabbar/index.js @@ -39,9 +39,9 @@ class Tabbar extends Nerv.Component { this.customRoutes.push([key, customRoutes[key]]) } - list.forEach( item => { - if (item.pagePath.indexOf('/') !== 0){ - item.pagePath = "/" + item.pagePath + list.forEach(item => { + if (item.pagePath.indexOf('/') !== 0) { + item.pagePath = '/' + item.pagePath } }) diff --git a/packages/taro-components/src/components/video/controls.js b/packages/taro-components/src/components/video/controls.js new file mode 100644 index 000000000000..c3cfcf49e5c6 --- /dev/null +++ b/packages/taro-components/src/components/video/controls.js @@ -0,0 +1,171 @@ +import Nerv, { Component } from 'nervjs' +import classnames from 'classnames' +import { formatTime, calcDist } from './utils' + +/** + * @typedef {Object} ControlsProps + * @property {Boolean} controls={controls} + * @property {Number} currentTime={this.currentTime} + * @property {Number} duration={this.state.duration} + * @property {Boolean} isPlaying={this.state.isPlaying} + * @property {Function} pauseFunc={this.pause} + * @property {Function} playFunc={this.play} + * @property {Function} seekFunc={this.seek} + * @property {Boolean} showPlayBtn={showPlayBtn} + * @property {Boolean} showProgress={showProgress} + */ +class Controls extends Component { + visible = false + isDraggingProgressBall = false + + /** @type {number} */ + hideControlsTimer + + /** @type {ControlsProps} */ + props + + progressDimentions = { + left: 0, + right: 0, + width: 0 + } + + calcPercentage = pageX => { + let pos = pageX - this.progressDimentions.left + pos = Math.max(pos, 0) + pos = Math.min(pos, this.progressDimentions.width) + return pos / this.progressDimentions.width + } + + getControlsRef = ref => { + if (!ref) return + this.controlsRef = ref + } + getCurrentTimeRef = ref => { + if (!ref) return + this.currentTimeRef = ref + } + getProgressBallRef = ref => { + if (!ref) return + this.progressBallRef = ref + } + + setCurrentTime (time) { + this.currentTimeRef.innerHTML = formatTime(time) + } + setProgressBall (percentage) { + this.progressBallRef.style.left = `${percentage * 100}%` + } + + toggleVisible (nextVisible) { + const visible = nextVisible === undefined ? !this.visible : nextVisible + if (visible) { + this.hideControlsTimer && clearTimeout(this.hideControlsTimer) + if (this.props.isPlaying) { + this.hideControlsTimer = setTimeout(() => { + this.toggleVisible(false) + }, 2000) + } + this.controlsRef.style.visibility = 'visible' + } else { + this.controlsRef.style.visibility = 'hidden' + } + this.visible = !!visible + } + + onDragProgressBallStart = () => { + this.isDraggingProgressBall = true + this.hideControlsTimer && clearTimeout(this.hideControlsTimer) + } + onClickProgress = e => { + e.stopPropagation() + const seekFunc = this.props.seekFunc + const percentage = this.calcPercentage(e.pageX) + seekFunc(percentage * this.props.duration) + this.toggleVisible(true) + } + bindTouchEvents = () => { + let percentage = 0 + const touchMove = e => { + if (!this.isDraggingProgressBall) return + const touchX = e.touches[0].pageX + percentage = this.calcPercentage(touchX) + this.setProgressBall(percentage) + } + const touchEnd = e => { + if (!this.isDraggingProgressBall) return + const seekFunc = this.props.seekFunc + this.isDraggingProgressBall = false + seekFunc(percentage * this.props.duration) + this.toggleVisible(true) + } + + document.body.addEventListener('touchmove', touchMove) + document.body.addEventListener('touchend', touchEnd) + document.body.addEventListener('touchcancel', touchEnd) + return () => { + document.body.removeEventListener('touchmove', touchMove) + document.body.removeEventListener('touchend', touchEnd) + document.body.removeEventListener('touchcancel', touchEnd) + } + } + + componentDidMount () { + this.unbindTouchEvents = this.bindTouchEvents() + } + componentWillUnmount () { + this.unbindTouchEvents() + } + + render () { + const { controls, currentTime, duration, isPlaying, pauseFunc, playFunc, showPlayBtn, showProgress } = this.props + const formattedDuration = formatTime(duration) + let playBtn + + if (!showPlayBtn) { + return null + } else if (isPlaying) { + playBtn =
+ } else { + playBtn =
+ } + + return ( +
+ {controls && ( +
+ {playBtn} + {showProgress && ( +
+ {formatTime(currentTime)} +
+ )} + {showProgress && ( +
+
{ + if (ref !== null) { + const rect = ref.getBoundingClientRect() + this.progressDimentions.left = rect.left + this.progressDimentions.right = rect.right + this.progressDimentions.width = rect.width + } + }}> +
+
+
+
+
+
+ )} + {showProgress &&
{formattedDuration}
} +
+ )} + {this.props.children} +
+ ) + } +} + +export default Controls diff --git a/packages/taro-components/src/components/video/danmu.js b/packages/taro-components/src/components/video/danmu.js new file mode 100644 index 000000000000..29e685d518cd --- /dev/null +++ b/packages/taro-components/src/components/video/danmu.js @@ -0,0 +1,107 @@ +import Nerv, { PureComponent } from 'nervjs' + +class Danmu extends PureComponent { + state = { + danmuList: [] + } + + danmuList = [] + danmuElList = [] + currentTime = 0 + + ensureProperties (danmu) { + const clonedDanmu = {...danmu} + if (!('time' in danmu)) { + clonedDanmu.time = this.currentTime + } + clonedDanmu.key = Math.random() + clonedDanmu.bottom = `${Math.random() * 90 + 5}%` + return clonedDanmu + } + + sendDanmu (danmuList) { + if (Array.isArray(danmuList)) { + this.danmuList = [ + ...this.danmuList, + ...danmuList.map(danmu => { + return this.ensureProperties(danmu) + }) + ] + } else { + const danmu = danmuList + this.danmuList = [ + ...this.danmuList, + { ...this.ensureProperties(danmu) } + ] + } + } + + tick (currentTime) { + this.currentTime = currentTime + if (!this.props.enable) return + + const danmuList = this.danmuList + /** + * @todo 这个判断对拖拽进度的处理不严谨 + */ + const newDanmuList = danmuList.filter(({ time }) => { + return currentTime - time < 4 && currentTime > time + }) + let shouldUpdate = false + const oldDanmuList = this.state.danmuList + + if (newDanmuList.length !== oldDanmuList.length) { + shouldUpdate = true + } else { + shouldUpdate = newDanmuList.some(({ key }) => { + return oldDanmuList.every((danmu) => { + return key !== danmu.key + }) + }) + } + if (shouldUpdate) { + this.setState({ + danmuList: newDanmuList + }) + } + } + + componentDidUpdate () { + requestAnimationFrame(() => { + setTimeout(() => { + const danmuElList = this.danmuElList.splice(0) + danmuElList.forEach(danmu => { + danmu.style.left = 0 + danmu.style.webkitTransform = 'translateX(-100%)' + danmu.style.transform = 'translateX(-100%)' + }) + }) + }) + } + + render () { + if (!this.props.enable) return '' + return
+ {this.state.danmuList.map(({ text, color, bottom, key }) => { + return ( +

{ + if (ref) { + this.danmuElList.push(ref) + } + }}> + {text} +

+ ) + })} +
+ } +} + +export default Danmu diff --git a/packages/taro-components/src/components/video/images/full.png b/packages/taro-components/src/components/video/images/full.png new file mode 100644 index 000000000000..9d1ec07a6533 Binary files /dev/null and b/packages/taro-components/src/components/video/images/full.png differ diff --git a/packages/taro-components/src/components/video/images/mute.png b/packages/taro-components/src/components/video/images/mute.png new file mode 100644 index 000000000000..158b42685631 Binary files /dev/null and b/packages/taro-components/src/components/video/images/mute.png differ diff --git a/packages/taro-components/src/components/video/images/pause.png b/packages/taro-components/src/components/video/images/pause.png new file mode 100644 index 000000000000..8a2afc2ca759 Binary files /dev/null and b/packages/taro-components/src/components/video/images/pause.png differ diff --git a/packages/taro-components/src/components/video/images/play.png b/packages/taro-components/src/components/video/images/play.png new file mode 100644 index 000000000000..ceabd9faaa77 Binary files /dev/null and b/packages/taro-components/src/components/video/images/play.png differ diff --git a/packages/taro-components/src/components/video/images/shrink.png b/packages/taro-components/src/components/video/images/shrink.png new file mode 100644 index 000000000000..2cd56f85208f Binary files /dev/null and b/packages/taro-components/src/components/video/images/shrink.png differ diff --git a/packages/taro-components/src/components/video/images/unmute.png b/packages/taro-components/src/components/video/images/unmute.png new file mode 100644 index 000000000000..572b201e1edc Binary files /dev/null and b/packages/taro-components/src/components/video/images/unmute.png differ diff --git a/packages/taro-components/src/components/video/images/volume.png b/packages/taro-components/src/components/video/images/volume.png new file mode 100644 index 000000000000..e4fbbaeebbdb Binary files /dev/null and b/packages/taro-components/src/components/video/images/volume.png differ diff --git a/packages/taro-components/src/components/video/index.js b/packages/taro-components/src/components/video/index.js index e9a85af16abc..b5d545cff400 100644 --- a/packages/taro-components/src/components/video/index.js +++ b/packages/taro-components/src/components/video/index.js @@ -1,88 +1,500 @@ -import 'weui' -import Nerv from 'nervjs' +import Nerv, { Component, createPortal } from 'nervjs' +import classnames from 'classnames' +import Danmu from './danmu' +import Controls from './controls' +import { formatTime, calcDist, normalizeNumber } from './utils' + import './style/index.scss' +import 'weui' + +/** + * @typedef {Object} Danmu + * @property {string} text 弹幕文字 + * @property {string} color 弹幕颜色 + * @property {number} [time] 弹幕时间 + */ -class Video extends Nerv.Component { - constructor () { - super(...arguments) +/** + * Video组件参数 + * @typedef {Object} VideoProps + * @property {string} src 要播放视频的资源地址 + * @property {boolean} [autoPauseIfNavigate=true] 当跳转到其它小程序页面时,是否自动暂停本页面的视频 + * @property {boolean} [autoPauseIfOpenNative=true] 当跳转到其它微信原生页面时,是否自动暂停本页面的视频 + * @property {boolean} [autoplay=false] 是否自动播放 + * @property {boolean} [controls=true] 是否显示默认播放控件(播放/暂停按钮、播放进度、时间) + * @property {boolean} [danmuBtn=false] 是否显示弹幕按钮,只在初始化时有效,不能动态变更 + * @property {Array.} [danmuList=[]] 弹幕列表 + * @property {boolean} [enableDanmu=false] 是否展示弹幕,只在初始化时有效,不能动态变更 + * @property {boolean} [enablePlayGesture=false] 是否开启播放手势,即双击切换播放/暂停 + * @property {boolean} [enableProgressGesture=true] 是否开启控制进度的手势 + * @property {number} [initialTime=0] 指定视频初始播放位置 + * @property {boolean} [loop=false] 是否循环播放 + * @property {boolean} [muted=false] 是否静音播放 + * @property {string} [objectFit=contain] 当视频大小与 video 容器大小不一致时,视频的表现形式 + * @property {string} [playBtnPosition=bottom] 播放按钮的位置 + * @property {boolean} [showCenterPlayBtn=true] 是否显示视频中间的播放按钮 + * @property {boolean} [showFullscreenBtn=true] 是否显示全屏按钮 + * @property {boolean} [showMuteBtn=false] 是否显示静音按钮 + * @property {boolean} [showPlayBtn=true] 是否显示视频底部控制栏的播放按钮 + * @property {boolean} [showProgress=true] 若不设置,宽度大于240时才会显示 + * @property {boolean} [vslideGesture=false] 在非全屏模式下,是否开启亮度与音量调节手势(同 pageGesture) + * @property {boolean} [vslideGestureInFullscreen=true] 在全屏模式下,是否开启亮度与音量调节手势 + * @property {number} [direction] 设置全屏时视频的方向,不指定则根据宽高比自动判断 + * @property {number} [duration] 指定视频时长 + * @property {string} [poster] 视频封面的图片网络资源地址。若 controls 属性值为 false 则设置 poster 无效 + * @property {string} [title] 视频的标题,全屏时在顶部展示 + * @property {Function} [onPlay] 当开始/继续播放时触发play事件 + * @property {Function} [onPause] 当暂停播放时触发 pause 事件 + * @property {Function} [onEnded] 当播放到末尾时触发 ended 事件 + * @property {Function} [onTimeUpdate] 播放进度变化时触发,event.detail = {currentTime, duration} 。触发频率 250ms 一次 + * @property {Function} [onFullscreenChange] 视频进入和退出全屏时触发,event.detail = {fullScreen, direction},direction 有效值为 vertical 或 horizontal + * @property {Function} [onWaiting] 视频出现缓冲时触发 + * @property {Function} [onError] 视频播放出错时触发 + * @property {Function} [onProgress] 加载进度变化时触发,只支持一段加载。event.detail = {buffered},百分比 + */ + +class Video extends Component { + /** @type {VideoProps} */ + static defaultProps = { + autoPauseIfNavigate: true, + autoPauseIfOpenNative: true, + autoplay: false, + controls: true, + danmuBtn: false, + danmuList: [], + enableDanmu: false, + enablePlayGesture: false, + enableProgressGesture: true, + initialTime: 0, + loop: false, + muted: false, + objectFit: 'contain', + playBtnPosition: 'bottom', + showCenterPlayBtn: true, + showFullscreenBtn: true, + showMuteBtn: false, + showPlayBtn: true, + showProgress: true, + vslideGesture: false, + vslideGestureInFullscreen: true } - componentDidMount () { - this.bindevent() + + /** @type {VideoProps} */ + props + + /** @type {HTMLVideoElement} */ + videoRef + + /** @type {Contorls} */ + controlsRef + + /** @type {HTMLDivElement} */ + currentTimeRef + + /** @type {HTMLDivElement} */ + danmuRef + + /** @type {number} */ + currentTime = 0 + + /** @type {number} */ + lastClickedTime + + /** @type {number} */ + lastTouchScreenX + + /** @type {number} */ + lastTouchScreenY + + progressDimentions = { + left: 0, + right: 0, + width: 0 } - bindevent () { - this.video.addEventListener('timeupdate', (e) => { - Object.defineProperty(e, 'detail', { - enumerable: true, - value: { - duration: e.srcElement.duration, - currentTime: e.srcElement.currentTime - } + constructor (props, context) { + super(props, context) + const stateObj = this.getInitialState(this.props) + this.state = Object.assign( + { + duration: null, + isPlaying: false, + isFirst: true, + enableDanmu: false, + isFullScreen: false, + isMute: false + }, + stateObj + ) + } + + sendDanmu (danmu) { + this.danmuRef.sendDanmu(danmu) + } + + onTimeUpdate = e => { + Object.defineProperty(e, 'detail', { + enumerable: true, + value: { + duration: e.srcElement.duration, + currentTime: e.srcElement.currentTime + } + }) + this.currentTime = this.videoRef.currentTime + const duration = this.state.duration + if (!this.controlsRef.isDraggingProgressBall && !this.isDraggingProgress) { + this.controlsRef.setProgressBall(this.currentTime / duration) + } + this.controlsRef.setCurrentTime(this.currentTime) + + this.danmuRef.tick(this.currentTime) + this.props.onTimeUpdate && this.props.onTimeUpdate(e) + } + + onEnded = e => { + this.pause() + this.props.onEnded && this.props.onEnded(e) + } + + onPlay = e => { + this.props.onPlay && this.props.onPlay(e) + this.controlsRef.toggleVisible(true) + if (!this.state.isPlaying) { + this.setState({ + isPlaying: true + }) + } + } + + onPause = e => { + this.props.onPause && this.props.onPause(e) + this.controlsRef.toggleVisible(true) + if (this.state.isPlaying) { + this.setState({ + isPlaying: false }) - this.props.onTimeUpdate && this.props.onTimeUpdate(e) + } + } + + onError = e => { + Object.defineProperty(e, 'detail', { + enumerable: true, + value: { errMsg: e.srcElement.error.code } }) + this.props.onError && this.props.onError(e) + } - this.video.addEventListener('ended', (e) => { - this.props.onEnded && this.props.onEnded(e) + onClickContainer = e => { + if (this.props.enablePlayGesture) { + const now = Date.now() + if (now - this.lastClickedTime < 300) { + // 双击 + if (this.state.isPlaying) { + this.pause() + } else { + this.play() + } + } + this.lastClickedTime = now + } + this.controlsRef.toggleVisible() + } + + onLoadedMetadata = e => { + this.setState({ + duration: this.videoRef.duration }) + this.duration = this.videoRef.duration + if (this.state.isFirst) { + this.seek(this.props.initialTime) + } + } - this.video.addEventListener('play', (e) => { - this.props.onPlay && this.props.onPlay(e) + toggleDanmu = e => { + e.stopPropagation() + this.controlsRef.toggleVisible(true) + this.setState({ + enableDanmu: !this.state.enableDanmu }) + } + + toggleFullScreen = e => { + e.stopPropagation() + const currentTime = this.currentTime + const danmuList = this.danmuRef.danmuList + this.setState( + { + isFullScreen: !this.state.isFullScreen + }, + () => { + const evt = new Event('fullscreenChange', { + fullScreen: this.state.isFullScreen, + direction: 'vertical' + }) + this.props.onFullscreenChange && this.props.onFullscreenChange(evt) + this.danmuRef.danmuList = danmuList + this.seek(currentTime) + this.state.isPlaying && this.play() + this.controlsRef.toggleVisible(true) + } + ) + } - this.video.addEventListener('pause', (e) => { - this.props.onPause && this.props.onPause(e) + toggleMute = e => { + e.stopPropagation() + this.setState(() => { + const nextMuteState = !this.state.isMute + this.videoRef.muted = nextMuteState + this.controlsRef.toggleVisible(true) + return { isMute: nextMuteState } }) + } - // 网络错误 - this.video.addEventListener('error', (e) => { - Object.defineProperty(e, 'detail', { - enumerable: true, - value: {errMsg: e.srcElement.error.code} - }) - this.props.onError && this.props.onError(e) + play = () => { + this.videoRef.play() + this.setState({ + isPlaying: true, + isFirst: false }) } + pause = () => { + this.videoRef.pause() + this.setState({ + isPlaying: false + }) + } + + seek = position => { + this.videoRef.currentTime = position + } + + getInitialState (props) { + const stateObj = { + enableDanmu: props.enableDanmu + } + return stateObj + } + + onTouchStartContainer = e => { + this.lastTouchScreenX = e.touches[0].screenX + this.lastTouchScreenY = e.touches[0].screenY + } + + bindTouchEvents = () => { + let lastVolume + let lastPercentage + let nextPercentage + let gestureType = 'none' + + const analyseGesture = e => { + const obj = {} + const nowX = e.touches[0].screenX + const nowY = e.touches[0].screenY + const distX = nowX - this.lastTouchScreenX + const distY = nowY - this.lastTouchScreenY + if (gestureType === 'none') { + const dist = calcDist(distX, distY) + if (dist < 10) { + obj.type = 'none' + return obj + } + if (distX === 0 || Math.abs(distY / distX) > 1) { + let enableVslideGesture = this.state.isFullScreen ? this.props.vslideGestureInFullscreen : this.props.vslideGesture + if (enableVslideGesture) { + gestureType = 'adjustVolume' + lastVolume = this.videoRef.volume + } + } else if (this.props.enableProgressGesture && Math.abs(distY / distX) <= 1) { + gestureType = 'adjustProgress' + lastPercentage = this.currentTime / this.state.duration + } + } + obj.type = gestureType + obj.dataX = normalizeNumber(distX / window.screen.width) + obj.dataY = normalizeNumber(distY / window.screen.height) + return obj + } + const touchMove = e => { + if (this.controlsRef.isDraggingProgressBall) return + + const gestureObj = analyseGesture(e) + if (gestureObj.type === 'adjustVolume') { + this.toastVolumeRef.style.visibility = 'visible' + const nextVolume = Math.max(Math.min(lastVolume - gestureObj.dataY, 1), 0) + this.videoRef.volume = nextVolume + this.toastVolumeBarRef.style.width = `${nextVolume * 100}%` + } else if (gestureObj.type === 'adjustProgress') { + this.isDraggingProgress = true + nextPercentage = Math.max(Math.min(lastPercentage + gestureObj.dataX, 1), 0) + this.controlsRef.setProgressBall(nextPercentage) + this.controlsRef.toggleVisible(true) + this.toastProgressTitleRef.innerHTML = `${formatTime(nextPercentage * this.duration)} / ${formatTime(this.duration)}` + this.toastProgressRef.style.visibility = 'visible' + } + } + const touchEnd = e => { + if (gestureType === 'adjustVolume') { + this.toastVolumeRef.style.visibility = 'hidden' + } else if (gestureType === 'adjustProgress') { + this.toastProgressRef.style.visibility = 'hidden' + } + gestureType = 'none' + if (this.isDraggingProgress) { + this.isDraggingProgress = false + this.seek(nextPercentage * this.videoRef.duration) + } + } + + document.body.addEventListener('touchmove', touchMove) + document.body.addEventListener('touchend', touchEnd) + document.body.addEventListener('touchcancel', touchEnd) + return () => { + document.body.removeEventListener('touchmove', touchMove) + document.body.removeEventListener('touchend', touchEnd) + document.body.removeEventListener('touchcancel', touchEnd) + } + } + + componentWillMount () { + const getRef = refName => { + return ref => { + if (!ref) return + this[refName] = ref + } + } + this.getVideoRef = getRef('videoRef') + this.getControlsRef = getRef('controlsRef') + this.getDanmuRef = getRef('danmuRef') + this.getToastProgressRef = getRef('toastProgressRef') + this.getToastProgressTitleRef = getRef('toastProgressTitleRef') + this.getToastVolumeRef = getRef('toastVolumeRef') + this.getToastVolumeBarRef = getRef('toastVolumeBarRef') + } + + componentDidMount () { + this.unbindTouchEvents = this.bindTouchEvents() + this.sendDanmu(this.props.danmuList) + } + + componentWillReceiveProps (nProps) { + const nState = this.getInitialState(nProps) + this.setState(nState) + } + + componentWillUnmount () { + this.unbindTouchEvents() + } + render () { - let { + const { src, - controls, autoplay, - poster, - initialTime, + className, id, + initialTime, loop, muted, - className + objectFit, + poster, + + controls, + showFullscreenBtn, + showMuteBtn, + showPlayBtn, + showProgress, + showCenterPlayBtn, + danmuBtn } = this.props - if (!controls) { - poster = '' + const { enableDanmu, isFirst, isMute, isFullScreen } = this.state + const duration = formatTime(this.state.duration) + + const videoProps = { + id, + src, + autoplay, + poster: controls ? poster : null, + loop, + muted, + start: initialTime, + className: classnames('taro-video-video', className), + ref: this.getVideoRef, + playsinline: true, + 'webkit-playsinline': true, + 'object-fit': objectFit, + + controls: false, + onTimeUpdate: this.onTimeUpdate, + onEnded: this.onEnded, + onPlay: this.onPlay, + onPause: this.onPause, + onError: this.onError, + onDurationChange: this.onLoadedMetadata } - return ( - + + const videoNode = ( +
+ + + {showMuteBtn && ( +
+ )} + {danmuBtn && ( +
+ 弹幕 +
+ )} + {showFullscreenBtn && ( +
+ )} + + + {isFirst && showCenterPlayBtn && !this.state.isPlaying && ( +
+
+

{duration}

+
+ )} +
+
音量
+
+
+
+
+ {new Array(10).fill().map(v => ( +
+ ))} +
+
+
+
+
+
+
+
) + return this.state.isFullScreen ? createPortal(videoNode, document.body) :
{videoNode}
} } -// 默认配置 -Video.defaultProps = { - autoplay: false, - controls: true, - loop: false, - muted: false -} - export default Video diff --git a/packages/taro-components/src/components/video/index.md b/packages/taro-components/src/components/video/index.md index c4132aec0d2f..b767442a6ce6 100644 --- a/packages/taro-components/src/components/video/index.md +++ b/packages/taro-components/src/components/video/index.md @@ -2,15 +2,37 @@ | 是否支持 | 属性 | 类型 | 默认值 | 说明 | | -------- | -------------- | ----------- | ------ | ------------------------------------------------------------ | -| √ | src | String | | 要播放视频的资源地址 | -| √ | controls | Boolean | true | 是否显示默认播放控件(播放/暂停按钮、播放进度、时间) | -| √ | autoplay | Boolean | false | 是否自动播放 | -| √ | poster | String | | 视频封面的图片网络资源地址,如果 controls 属性值为 false 则设置 poster 无效 | -| | initial-time | Number | | 指定视频初始播放位置 | -| √ | loop | Boolean | false | 是否循环播放 | -| √ | muted | Boolean | false | 是否静音播放 | -| √ | bindplay | EventHandle | | 当开始/继续播放时触发play事件 | -| √ | bindpause | EventHandle | | 当暂停播放时触发 pause 事件 | -| √ | bindended | EventHandle | | 当播放到末尾时触发 ended 事件 | -| √ | bindtimeupdate | EventHandle | | 播放进度变化时触发。触发频率 250ms 一次 | -| √ | binderror | EventHandle | | 视频播放出错时触发 | +| | autoPauseIfNavigate | boolean | true |当跳转到其它小程序页面时,是否自动暂停本页面的视频 +| | autoPauseIfOpenNative | boolean | true |当跳转到其它微信原生页面时,是否自动暂停本页面的视频 +| | direction | number | |设置全屏时视频的方向,不指定则根据宽高比自动判断 +| √ | autoplay | boolean | false |是否自动播放 +| √ | controls | boolean | true |是否显示默认播放控件(播放/暂停按钮、播放进度、时间) +| √ | danmuBtn | boolean | false |是否显示弹幕按钮,只在初始化时有效,不能动态变更 +| √ | danmuList | Array. | [] |弹幕列表 +| √ | duration | number | |指定视频时长 +| √ | enableDanmu | boolean | false |是否展示弹幕,只在初始化时有效,不能动态变更 +| √ | enablePlayGesture | boolean | false |是否开启播放手势,即双击切换播放/暂停 +| √ | enableProgressGesture | boolean | true |是否开启控制进度的手势 +| √ | initialTime | number | 0 |指定视频初始播放位置 +| √ | loop | boolean | false |是否循环播放 +| √ | muted | boolean | false |是否静音播放 +| √ | objectFit | string | contain |当视频大小与 video 容器大小不一致时,视频的表现形式 +| √ | onEnded | Function | |当播放到末尾时触发 ended 事件 +| √ | onError | Function | |视频播放出错时触发 +| √ | onFullscreenChange | Function | |视频进入和退出全屏时触发,event.detail = {fullScreen, direction},direction 有效值为 vertical 或 horizontal +| √ | onPause | Function | |当暂停播放时触发 pause 事件 +| √ | onPlay | Function | |当开始/继续播放时触发play事件 +| √ | onProgress | Function | |加载进度变化时触发,只支持一段加载。event.detail = {buffered},百分比 +| √ | onTimeUpdate | Function | |播放进度变化时触发,event.detail = {currentTime, duration}。触发频率 250ms 一次 +| √ | onWaiting | Function | |视频出现缓冲时触发 +| √ | playBtnPosition | string | bottom |播放按钮的位置 +| √ | poster | string | |视频封面的图片网络资源地址。若 controls 属性值为 false 则设置 poster 无效 +| √ | showCenterPlayBtn | boolean | true |是否显示视频中间的播放按钮 +| √ | showFullscreenBtn | boolean | true |是否显示全屏按钮 +| √ | showMuteBtn | boolean | false |是否显示静音按钮 +| √ | showPlayBtn | boolean | true |是否显示视频底部控制栏的播放按钮 +| √ | showProgress | boolean | true |若不设置,宽度大于240时才会显示 +| √ | src | string | |要播放视频的资源地址 +| √ | title | string | |视频的标题,全屏时在顶部展示 +| √ | vslideGesture | boolean | false |在非全屏模式下,是否开启亮度与音量调节手势(同 pageGesture) +| √ | vslideGestureInFullscreen | boolean | true |在全屏模式下,是否开启亮度与音量调节手势 diff --git a/packages/taro-components/src/components/video/style/index.scss b/packages/taro-components/src/components/video/style/index.scss index 016922c28efb..e2c048ba8c32 100644 --- a/packages/taro-components/src/components/video/style/index.scss +++ b/packages/taro-components/src/components/video/style/index.scss @@ -1,6 +1,349 @@ @charset "UTF-8"; -video { - max-width:100%; - height:auto; +.taro-video { + // width: 300px; + width: 100%; + height: 225px; + display: inline-block; + line-height: 0; + overflow: hidden; + position: relative } + +.taro-video[hidden] { + display: none +} + +.taro-video-container { + width: 100%; + height: 100%; + background-color: #000; + display: inline-block; + position: absolute; + top: 0; + left: 0; + overflow: hidden; + object-position: inherit +} + +.taro-video-container.taro-video-type-fullscreen { + position: fixed; + top: 50%; + left: 50%; + -webkit-transform: translate(-50%,-50%); + transform: translate(-50%,-50%); + z-index: 999 +} + +.taro-video-container.taro-video-type-fullscreen.taro-video-type-rotate-left { + -webkit-transform: translate(-50%,-50%) rotate(-90deg); + transform: translate(-50%,-50%) rotate(-90deg) +} + +.taro-video-container.taro-video-type-fullscreen.taro-video-type-rotate-right { + -webkit-transform: translate(-50%,-50%) rotate(90deg); + transform: translate(-50%,-50%) rotate(90deg) +} + +.taro-video-video { + width: 100%; + height: 100%; + object-position: inherit +} + +.taro-video-cover { + position: absolute; + top: 0; + left: 0; + bottom: 0; + width: 100%; + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -webkit-flex-direction: column; + flex-direction: column; + -webkit-box-pack: center; + -webkit-justify-content: center; + justify-content: center; + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center; + background-color: rgba(1,1,1,.5); + z-index: 1 +} + +.taro-video-cover-play-button { + width: 40px; + height: 40px; + background-size: 50%; + background-repeat: no-repeat; + background-position: 50% 50% +} + +.taro-video-cover-duration { + color: #fff; + font-size: 16px; + line-height: 1; + margin-top: 10px +} + +.taro-video-bar { + visibility: hidden; + height: 44px; + background-color: rgba(0,0,0,.5); + overflow: hidden; + position: absolute; + bottom: 0; + right: 0; + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center; + padding: 0 10px; + z-index: 0 +} + +.taro-video-bar.taro-video-bar-full { + left: 0 +} + +.taro-video-controls { + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-flex: 1; + -webkit-flex-grow: 1; + flex-grow: 1; + margin: 0 8.5px +} + +.taro-video-control-button { + width: 13px; + height: 15px; + padding: 14.5px 12.5px 14.5px 12.5px; + margin-left: -8.5px; + box-sizing: content-box +} + +.taro-video-control-button:after { + content: ""; + display: block; + width: 100%; + height: 100%; + background-size: 100%; + background-position: 50% 50%; + background-repeat: no-repeat +} + +.taro-video-control-button.taro-video-control-button-play:after,.taro-video-cover-play-button { + background-image: url("../images/play.png") +} + +.taro-video-control-button.taro-video-control-button-pause:after { + background-image: url("../images//pause.png") +} + +.taro-video-current-time,.taro-video-duration { + height: 14.5px; + line-height: 14.5px; + margin-top: 15px; + margin-bottom: 14.5px; + font-size: 12px; + color: #cbcbcb +} + +.taro-video-progress-container { + -webkit-box-flex: 2; + -webkit-flex-grow: 2; + flex-grow: 2; + position: relative +} + +.taro-video-progress { + height: 2px; + margin: 21px 12px; + background-color: hsla(0,0%,100%,.4); + position: relative +} + +.taro-video-progress-buffered { + position: absolute; + left: 0; + top: 0; + width: 0; + height: 100%; + -webkit-transition: width .1s; + transition: width .1s; + background-color: hsla(0,0%,100%,.8) +} + +.taro-video-ball { + width: 16px; + height: 16px; + padding: 14px; + position: absolute; + top: -21px; + box-sizing: content-box; + left: 0; + margin-left: -22px +} + +.taro-video-inner { + width: 100%; + height: 100%; + background-color: #fff; + border-radius: 50% +} + +.taro-video-danmu-button { + white-space: nowrap; + line-height: 1; + padding: 2px 10px; + border: 1px solid #fff; + border-radius: 5px; + font-size: 13px; + color: #fff; + margin: 0 8.5px +} + +.taro-video-danmu-button.taro-video-danmu-button-active { + border-color: #48c23d; + color: #48c23d +} + +.taro-video-fullscreen, +.taro-video-mute { + width: 17px; + height: 17px; + padding: 8.5px; + box-sizing: content-box; + background-size: 50%; + background-position: 50% 50%; + background-repeat: no-repeat +} +.taro-video-fullscreen { + background-image: url("../images/full.png"); +} + +.taro-video-fullscreen.taro-video-type-fullscreen { + background-image: url("../images/shrink.png") +} + +.taro-video-mute { + background-image: url("../images/unmute.png"); +} + +.taro-video-mute.taro-video-type-mute { + background-image: url("../images/mute.png") +} + +.taro-video-danmu { + position: absolute; + top: 0; + left: 0; + bottom: 0; + width: 100%; + margin-top: 14px; + margin-bottom: 44px; + font-size: 14px; + line-height: 14px; + overflow: visible +} + +.taro-video-danmu-item { + line-height: 1; + position: absolute; + color: #fff; + white-space: nowrap; + left: 100%; + -webkit-transform: translatex(0); + transform: translatex(0); + -webkit-transition-property: left,-webkit-transform; + transition-property: left,-webkit-transform; + transition-property: left,transform; + transition-property: left,transform,-webkit-transform; + -webkit-transition-duration: 3s; + transition-duration: 3s; + -webkit-transition-timing-function: linear; + transition-timing-function: linear +} + +.taro-video-toast { + pointer-events: none; + position: absolute; + left: 50%; + top: 50%; + -webkit-transform: translate(-50%,-50%); + transform: translate(-50%,-50%); + border-radius: 5px; + background-color: hsla(0,0%,100%,.8); + color: #000; + display: block; + visibility: hidden; +} + +.taro-video-toast.taro-video-toast-volume { + width: 100px; + height: 100px; + display: block +} + +.taro-video-toast-volume .taro-video-toast-title { + width: 100%; + font-size: 12px; + line-height: 16px; + text-align: center; + margin-top: 10px; + display: block +} + +.taro-video-toast-volume .taro-video-toast-icon { + fill: #000; + width: 50%; + height: 50%; + margin-left: 25%; + display: block; + background-image: url('../images/volume.png'); + background-size: 50%; + background-position: 50% 50%; + background-repeat: no-repeat +} + +.taro-video-toast-volume .taro-video-toast-value { + width: 80px; + height: 5px; + margin-top: 5px; + margin-left: 10px +} + +.taro-video-toast-volume .taro-video-toast-value>.taro-video-toast-value-content { + overflow: hidden +} + +.taro-video-toast-volume-grids { + width: 80px; + height: 5px +} + +.taro-video-toast-volume-grids-item { + float: left; + width: 7.1px; + height: 5px; + background-color: #000 +} + +.taro-video-toast-volume-grids-item:not(:first-child) { + margin-left: 1px +} + +.taro-video-toast.taro-video-toast-progress { + background-color: rgba(0,0,0,.8); + color: #fff; + font-size: 14px; + line-height: 18px; + padding: 6px +} \ No newline at end of file diff --git a/packages/taro-components/src/components/video/utils.js b/packages/taro-components/src/components/video/utils.js new file mode 100644 index 000000000000..d1e52025fe6c --- /dev/null +++ b/packages/taro-components/src/components/video/utils.js @@ -0,0 +1,14 @@ +export const formatTime = time => { + if (time === null) return '' + const sec = Math.round(time % 60) + const min = Math.round((time - sec) / 60) + return `${min < 10 ? `0${min}` : min}:${sec < 10 ? `0${sec}` : sec}` +} + +export const calcDist = (x, y) => { + return Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)) +} + +export const normalizeNumber = number => { + return Math.max(-1, Math.min(number, 1)) +} diff --git a/packages/taro-components/src/utils/touchable.js b/packages/taro-components/src/utils/touchable.js index 2db90382e3cc..9bae3a73cc80 100644 --- a/packages/taro-components/src/utils/touchable.js +++ b/packages/taro-components/src/utils/touchable.js @@ -8,35 +8,35 @@ const touchable = (opt = { return ComponentClass => { return class TouchableComponent extends Taro.Component { static defaultProps = { - bindTouchStart: null, - bindTouchMove: null, - bindTouchEnd: null, - bindTouchCancel: null, - bindLongTap: null + onTouchStart: null, + onTouchMove: null, + onTouchEnd: null, + onTouchCancel: null, + onLongTap: null } timer = null onTouchStart = e => { - const { bindTouchStart, bindLongTap } = this.props - bindTouchStart && bindTouchStart(e) + const { onTouchStart, onLongTap } = this.props + onTouchStart && onTouchStart(e) this.timer = setTimeout(() => { - bindLongTap && bindLongTap(e) + onLongTap && onLongTap(e) }, opt.longTapTime) } onTouchMove = e => { this.timer && clearTimeout(this.timer) - const { bindTouchMove } = this.props - bindTouchMove && bindTouchMove(e) + const { onTouchMove } = this.props + onTouchMove && onTouchMove(e) } onTouchEnd = e => { this.timer && clearTimeout(this.timer) - const { bindTouchEnd } = this.props - bindTouchEnd && bindTouchEnd(e) + const { onTouchEnd } = this.props + onTouchEnd && onTouchEnd(e) } onTouchCancel = e => { this.timer && clearTimeout(this.timer) - const { bindTouchCancel } = this.props - bindTouchCancel && bindTouchCancel(e) + const { onTouchCancel } = this.props + onTouchCancel && onTouchCancel(e) } render () { const props = { @@ -45,11 +45,11 @@ const touchable = (opt = { onTouchEnd: this.onTouchEnd, onTouchCancel: this.onTouchCancel, ...omit(this.props, [ - 'bindTouchStart', - 'bindTouchMove', - 'bindTouchEnd', - 'bindTouchCancel', - 'bindLongTap' + 'onTouchStart', + 'onTouchMove', + 'onTouchEnd', + 'onTouchCancel', + 'onLongTap' ]) } return diff --git a/packages/taro-h5/src/api/index.js b/packages/taro-h5/src/api/index.js index b0f86fe6647d..ac1a638d160c 100644 --- a/packages/taro-h5/src/api/index.js +++ b/packages/taro-h5/src/api/index.js @@ -2,18 +2,19 @@ export * from './unsupportedApi' /* 已实现api */ -export * from './request' export * from './canvas' export * from './createAnimation' export * from './createSelectorQuery' -export * from './webSocket' -export * from './storage' +export * from './imageUtils' export * from './interactive' -export * from './tabBar' -export * from './system' -export * from './others' export * from './navigationBar' -export * from './imageUtils' +export * from './others' export * from './pullDownRefresh' +export * from './request' +export * from './storage' +export * from './system' +export * from './tabBar' +export * from './videoUtils' +export * from './webSocket' export * from './privateApis' diff --git a/packages/taro-h5/src/api/videoUtils/index.js b/packages/taro-h5/src/api/videoUtils/index.js new file mode 100644 index 000000000000..faffffacec55 --- /dev/null +++ b/packages/taro-h5/src/api/videoUtils/index.js @@ -0,0 +1,63 @@ +import { shouleBeObject, getParameterError } from '../utils' + +export function chooseImage (options) { + // options must be an Object + const isObject = shouleBeObject(options) + if (!isObject.res) { + const res = { errMsg: `chooseImage${isObject.msg}` } + console.error(res.errMsg) + return Promise.reject(res) + } + + const { count = 1, success, fail, complete } = options + const res = { + errMsg: 'chooseImage:ok', + tempFilePaths: [], + tempFiles: [] + } + + if (count && typeof count !== 'number') { + res.errMsg = getParameterError({ + name: 'chooseImage', + para: 'count', + correct: 'Number', + wrong: count + }) + console.error(res.errMsg) + typeof fail === 'function' && fail(res) + typeof complete === 'function' && complete(res) + return Promise.reject(res) + } + + let taroChooseImageId = document.getElementById('taroChooseImage') + if (!taroChooseImageId) { + let obj = document.createElement('input') + obj.setAttribute('type', 'file') + obj.setAttribute('id', 'taroChooseImage') + obj.setAttribute('multiple', 'multiple') + obj.setAttribute('accept', 'image/*') + obj.setAttribute('style', 'position: fixed; top: -4000px; left: -3000px; z-index: -300;') + document.body.appendChild(obj) + taroChooseImageId = document.getElementById('taroChooseImage') + } + let taroChooseImageCallback + const taroChooseImagePromise = new Promise(resolve => { + taroChooseImageCallback = resolve + }) + let TaroMouseEvents = document.createEvent('MouseEvents') + TaroMouseEvents.initEvent('click', true, true) + taroChooseImageId.dispatchEvent(TaroMouseEvents) + taroChooseImageId.onchange = function (e) { + let arr = Array.from(e.target.files) + arr && arr.forEach(item => { + let blob = new Blob([item]) + let url = URL.createObjectURL(blob) + res.tempFilePaths.push(url) + res.tempFiles.push({path: url, size: item.size, type: item.type}) + }) + typeof success === 'function' && success(res) + typeof complete === 'function' && complete(res) + taroChooseImageCallback(res) + } + return taroChooseImagePromise +} diff --git a/packages/taro-router/src/router/route.tsx b/packages/taro-router/src/router/route.tsx index 7a7cfb4665c7..77b0a1bb3375 100644 --- a/packages/taro-router/src/router/route.tsx +++ b/packages/taro-router/src/router/route.tsx @@ -133,7 +133,10 @@ class Route extends Component { const WrappedComponent = this.wrappedComponent return ( -
+
) diff --git a/packages/taro-router/src/router/router.tsx b/packages/taro-router/src/router/router.tsx index b9551ed0010d..e5b928f3525d 100644 --- a/packages/taro-router/src/router/router.tsx +++ b/packages/taro-router/src/router/router.tsx @@ -142,7 +142,9 @@ class Router extends Component { const currentLocation = Taro._$router router.currentPages.length = this.state.routeStack.length return ( -
+
{this.state.routeStack.map(({ path, componentLoader, isIndex, key, isRedirect }, k) => { return ( {