video.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  1. <style>
  2. .__cov-video-container {
  3. position: relative;
  4. width: 100%;
  5. background-color: #000;
  6. }
  7. .__cov-video {
  8. width: 100%;
  9. height: 100%;
  10. vertical-align: bottom;
  11. }
  12. .__cov-contrl-content {
  13. position: absolute;
  14. display: flex;
  15. left: 0;
  16. bottom: 0;
  17. background-color: rgba(0, 0, 0, 0.41);
  18. height: 2rem;
  19. width: 100%;
  20. z-index: 2147483647;
  21. }
  22. .__cov-contrl-play-btn {
  23. position: relative;
  24. height: 100%;
  25. background: none;
  26. border: none;
  27. height: 2rem;
  28. width: 4rem;
  29. outline: none;
  30. vertical-align: top;
  31. }
  32. .__cov-contrl-play-btn:hover {
  33. background-color: rgba(255, 255, 255, 0.27);
  34. }
  35. .__cov-contrl-play-btn-icon {
  36. position: absolute;
  37. height: 1rem;
  38. width: 1rem;
  39. top: 50%;
  40. left: 50%;
  41. margin-top: -.5rem;
  42. margin-left: -.5rem;
  43. }
  44. .__cov-contrl-vol-btn-icon {
  45. position: absolute;
  46. height: 1.1rem;
  47. width: 1.1rem;
  48. top: 50%;
  49. left: 50%;
  50. margin-top: -.55rem;
  51. margin-left: -.55rem;
  52. }
  53. .__cov-contrl-vol-slider {
  54. position: relative;
  55. display: inline-block;
  56. height: 100%;
  57. width: 6rem;
  58. height: 2rem;
  59. overflow: hidden;
  60. transition: all .2s ease-in;
  61. }
  62. .__cov-contrl-vol-rail {
  63. position: absolute;
  64. top: 50%;
  65. width: 6rem;
  66. height: .1rem;
  67. margin-top: -.05rem;
  68. background: #fff;
  69. }
  70. .__cov-contrl-vol-inner {
  71. position: absolute;
  72. display: inline-block;
  73. left: 0;
  74. top: 50%;
  75. background: #fff;
  76. width: .5rem;
  77. height: .5rem;
  78. border-radius: 50%;
  79. margin-top: -.25rem;
  80. z-index: 2;
  81. cursor: pointer;
  82. }
  83. .__cov-contrl-vol-box {
  84. display: flex;
  85. }
  86. .__cov-contrl-video-slider {
  87. position: relative;
  88. display: inline-block;
  89. height: 100%;
  90. width: 100%;
  91. overflow: hidden;
  92. margin: 0 .5rem;
  93. transition: all .2s ease-in;
  94. }
  95. .__cov-contrl-video-rail {
  96. position: absolute;
  97. top: 50%;
  98. width: 100%;
  99. height: .1rem;
  100. margin-top: -.05rem;
  101. background: rgba(255, 255, 255, 0.5);
  102. overflow: hidden;
  103. }
  104. .__cov-contrl-video-rail-inner {
  105. position: absolute;
  106. top: 0;
  107. left: 0;
  108. width: 100%;
  109. height: .1rem;
  110. background: rgb(255, 255, 255);
  111. transition: transform .2s;
  112. }
  113. .__cov-contrl-video-inner {
  114. position: absolute;
  115. display: inline-block;
  116. left: 0;
  117. top: 50%;
  118. background: #fff;
  119. width: .5rem;
  120. height: .5rem;
  121. border-radius: 50%;
  122. margin-top: -.25rem;
  123. z-index: 2;
  124. cursor: pointer;
  125. transition: all 16ms;
  126. }
  127. .__cov-contrl-video-time {
  128. padding: 0 1rem;
  129. }
  130. .__cov-contrl-video-time-text {
  131. color: #fff;
  132. line-height: 2rem;
  133. font-size: .8rem;
  134. }
  135. ::-webkit-media-controls {
  136. display:none !important;
  137. }
  138. video::-webkit-media-controls {
  139. display:none !important;
  140. }
  141. video::-webkit-media-controls-enclosure {
  142. display:none !important;
  143. }
  144. .fade-transition {
  145. transition: opacity .3s ease;
  146. }
  147. .fade-enter{
  148. opacity: 1;
  149. }
  150. .fade-leave {
  151. opacity: 0;
  152. }
  153. .hide-cursor {
  154. cursor: none;
  155. }
  156. @media all and (max-width: 768px) {
  157. .__cov-contrl-vol-slider {
  158. width: 3rem;
  159. }
  160. .__cov-contrl-video-time {
  161. padding: 0 .2rem;
  162. }
  163. .__cov-contrl-vol-box .__cov-contrl-play-btn {
  164. width: 2rem;
  165. }
  166. }
  167. </style>
  168. <template>
  169. <div id="app">
  170. <div class="container">
  171. <div class="__cov-video-container" @mouseenter="mouseEnterVideo" @mouseleave="mouseLeaveVideo">
  172. <video :class="{ 'hide-cursor': !state.contrlShow }" class="__cov-video" :poster="options.poster">
  173. <source v-for="source in sources" :src="source.src" :type="source.type">
  174. </source>
  175. </video>
  176. <div class="__cov-contrl-content" transition="fade" v-show="state.contrlShow">
  177. <button class="__cov-contrl-play-btn" @click="play">
  178. <svg class="__cov-contrl-play-btn-icon" v-show="!state.playing" viewBox="0 0 47 57" version="1.1" xmlns="http://www.w3.org/2000/svg">
  179. <!-- Generator: Sketch 3.8.3 (29802) - http://www.bohemiancoding.com/sketch -->
  180. <title>Triangle 1</title>
  181. <desc>Created with Sketch.</desc>
  182. <defs></defs>
  183. <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
  184. <polygon id="Triangle-1" stroke="#FFFFFF" fill="#FFFFFF" points="1 56 1 1 47 28.5"></polygon>
  185. </g>
  186. </svg>
  187. <svg class="__cov-contrl-play-btn-icon" v-show="state.playing" viewBox="0 0 15 22" version="1.1" xmlns="http://www.w3.org/2000/svg">
  188. <!-- Generator: Sketch 3.8.3 (29802) - http://www.bohemiancoding.com/sketch -->
  189. <title>Combined Shape</title>
  190. <desc>Created with Sketch.</desc>
  191. <defs>
  192. <path d="M0,0.979149244 L5,0.979149244 L5,22 L0,22 L0,0.979149244 Z M10,0.979149244 L15,0.979149244 L15,22 L10,22 L10,0.979149244 Z" id="path-1"></path>
  193. <mask id="mask-2" maskContentUnits="userSpaceOnUse" maskUnits="objectBoundingBox" x="0" y="0" width="15" height="21.0208508" fill="white">
  194. <use xlink:href="#path-1"></use>
  195. </mask>
  196. </defs>
  197. <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
  198. <use id="Combined-Shape" stroke="#FFFFFF" mask="url(#mask-2)" stroke-width="2" fill="#FFFFFF" xlink:href="#path-1"></use>
  199. </g>
  200. </svg>
  201. </button>
  202. <div class="__cov-contrl-video-slider" @click="slideClick" @mousedown="videoMove">
  203. <div class="__cov-contrl-video-inner" :style="{ 'transform': `translate3d(${video.pos.current}px, 0, 0)`}"></div>
  204. <div class="__cov-contrl-video-rail">
  205. <div class="__cov-contrl-video-rail-inner" :style="{ 'transform': 'translate3d(' +video.loaded + '%, 0, 0)'}"></div>
  206. </div>
  207. </div>
  208. <div class="__cov-contrl-video-time">
  209. <span class="__cov-contrl-video-time-text">{{video.displayTime}}</span>
  210. </div>
  211. <div class="__cov-contrl-vol-box">
  212. <button class="__cov-contrl-play-btn" @click="volMuted">
  213. <svg class="__cov-contrl-vol-btn-icon" viewBox="0 0 41 44" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
  214. <!-- Generator: Sketch 3.8.3 (29802) - http://www.bohemiancoding.com/sketch -->
  215. <title>vol</title>
  216. <desc>Created with Sketch.</desc>
  217. <defs>
  218. <path d="M8.61522369,12 L20,0.615223689 L20,37.3847763 L8.61522369,26 L1.99201702,26 C0.891856397,26 0,25.1029399 0,23.9941413 L0,14.0058587 C0,12.8980535 0.900176167,12 1.99201702,12 L8.61522369,12 L8.61522369,12 Z" id="cov-vol"></path>
  219. </defs>
  220. <g id="Page-1" stroke="none" stroke-width="2" fill="none" fill-rule="evenodd">
  221. <g id="vol" transform="translate(2.000000, 3.000000)">
  222. <g id="cov-vol-icon">
  223. <g id="Combined-Shape-Clipped">
  224. <path v-show="volume.percent > 1 && !volume.muted" d="M25,29.5538997 C28.4589093,27.6757536 31.2629093,23.2984641 31.2629093,19.7769499 C31.2629093,16.2554357 28.4589093,11.8781461 25,10" id="vol-range-2" stroke="#FFFFFF"></path>
  225. <path v-show="volume.percent > 70 && !volume.muted" d="M28,35.5538997 C33.5816016,32.5231573 38.1063837,25.4595762 38.1063837,19.7769499 C38.1063837,14.0943235 33.5816016,7.03074247 28,4" id="vol-range-2" stroke="#FFFFFF"></path>
  226. <mask id="mask-2" fill="white">
  227. <use xlink:href="#cov-vol"></use>
  228. </mask>
  229. <use id="vol-path" stroke="#FFFFFF" stroke-width="3" xlink:href="#cov-vol"></use>
  230. <g id="Combined-Shape" mask="url(#mask-2)" stroke="#FFFFFF" stroke-width="2" fill="#FFFFFF">
  231. <path d="M8.61522369,12 L20,0.615223689 L20,37.3847763 L8.61522369,26 L1.99201702,26 C0.891856397,26 0,25.1029399 0,23.9941413 L0,14.0058587 C0,12.8980535 0.900176167,12 1.99201702,12 L8.61522369,12 L8.61522369,12 Z" id="cov-vol"></path>
  232. </g>
  233. </g>
  234. </g>
  235. </g>
  236. </g>
  237. </svg>
  238. </button>
  239. <div class="__cov-contrl-vol-slider" @click="volSlideClick" @mousedown="volMove">
  240. <div class="__cov-contrl-vol-inner" :style="{ 'transform': `translate3d(${volume.pos.current}px, 0, 0)`}"></div>
  241. <div class="__cov-contrl-vol-rail"></div>
  242. </div>
  243. </div>
  244. <button class="__cov-contrl-play-btn" @click="fullScreen">
  245. <svg class="__cov-contrl-vol-btn-icon" viewBox="0 0 33 33" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
  246. <!-- Generator: Sketch 3.8.3 (29802) - http://www.bohemiancoding.com/sketch -->
  247. <defs></defs>
  248. <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
  249. <path d="M31.1682064,22 L31.1682064,31.0073537 L22,31.0073537 M22,1 L31.0073537,1 L31.0073537,10.1682064 M1,10.0073537 L1,1 L10.1682064,1 M10.0073537,31.1682064 L1,31.1682064 L1,22" id="Combined-Shape" stroke="#FFFFFF" stroke-width="2"></path>
  250. </g>
  251. </svg>
  252. </button>
  253. </div>
  254. </div>
  255. </div>
  256. </div>
  257. </template>
  258. <script>
  259. const getMousePosition = function (e, type = 'x') {
  260. if (type === 'x') {
  261. return e.pageX
  262. }
  263. return e.pageY
  264. }
  265. const pad = (val) => {
  266. val = Math.floor(val)
  267. if (val < 10) {
  268. return '0' + val
  269. }
  270. return val + ''
  271. }
  272. const timeParse = (sec) => {
  273. let min = 0
  274. min = Math.floor(sec / 60)
  275. sec = sec - min * 60
  276. return pad(min) + ':' + pad(sec)
  277. }
  278. export default {
  279. props: {
  280. sources: Array,
  281. options: {
  282. type: Object,
  283. default () {
  284. return {
  285. autoplay: false,
  286. volume: 0.9,
  287. poster: ''
  288. }
  289. }
  290. }
  291. },
  292. data () {
  293. return {
  294. $video: null,
  295. video: {
  296. $videoSlider: null,
  297. len: 0,
  298. current: 0,
  299. loaded: 0,
  300. moving: false,
  301. displayTime: '00:00',
  302. pos: {
  303. start: 0,
  304. width: 0,
  305. innerWidth: 0,
  306. current: 0
  307. }
  308. },
  309. volume: {
  310. $volBox: null,
  311. muted: false,
  312. percent: 60,
  313. moving: false,
  314. pos: {
  315. start: 0,
  316. width: 0,
  317. innerWidth: 0,
  318. current: 0
  319. }
  320. },
  321. player: {
  322. $player: null,
  323. pos: null
  324. },
  325. tmp: {
  326. contrlHideTimer: null
  327. },
  328. state: {
  329. contrlShow: true,
  330. vol: 0.5,
  331. currentTime: 0,
  332. fullScreen: false,
  333. playing: false
  334. }
  335. }
  336. },
  337. ready () {
  338. this.$video = this.$el.getElementsByTagName('video')[0]
  339. this.init()
  340. if (this.options.autoplay) {
  341. this.play()
  342. }
  343. document.body.addEventListener('mousemove', this.mouseMoveAction, false)
  344. document.body.addEventListener('mouseup', this.mouseUpAction, false)
  345. },
  346. beforeDestroy () {
  347. document.body.removeEventListener('mousemove', this.mouseMoveAction)
  348. document.body.removeEventListener('mouseup', this.mouseUpAction)
  349. },
  350. methods: {
  351. init () {
  352. this.initVol()
  353. this.initVideo()
  354. this.initPlayer()
  355. const vol = this.options.volume || 0.9
  356. this.setVol(vol)
  357. },
  358. initPlayer () {
  359. const $player = this.$el.getElementsByClassName('__cov-video-container')[0]
  360. this.player.pos = $player.getBoundingClientRect()
  361. this.player.$player = $player
  362. },
  363. initVol () {
  364. const $volBox = this.$el.getElementsByClassName('__cov-contrl-vol-slider')[0]
  365. const $volInner = $volBox.getElementsByClassName('__cov-contrl-vol-inner')[0]
  366. this.volume.$volBox = $volBox
  367. this.volume.pos.innerWidth = $volInner.getBoundingClientRect().width
  368. this.volume.pos.start = $volBox.getBoundingClientRect().left
  369. this.volume.pos.width = $volBox.getBoundingClientRect().width - this.volume.pos.innerWidth
  370. },
  371. initVideo () {
  372. const $videoSlider = this.$el.getElementsByClassName('__cov-contrl-video-slider')[0]
  373. const $videoInner = $videoSlider.getElementsByClassName('__cov-contrl-video-inner')[0]
  374. this.$videoSlider = $videoSlider
  375. this.video.pos.start = $videoSlider.getBoundingClientRect().left
  376. this.video.pos.innerWidth = $videoInner.getBoundingClientRect().width
  377. this.video.pos.width = $videoSlider.getBoundingClientRect().width - this.video.pos.innerWidth
  378. this.getTime()
  379. },
  380. mouseEnterVideo () {
  381. if (this.tmp.contrlHideTimer) {
  382. clearTimeout(this.tmp.contrlHideTimer)
  383. this.tmp.contrlHideTimer = null
  384. }
  385. this.state.contrlShow = true
  386. },
  387. mouseLeaveVideo (e) {
  388. if (this.tmp.contrlHideTimer) {
  389. clearTimeout(this.tmp.contrlHideTimer)
  390. }
  391. this.tmp.contrlHideTimer = setTimeout(() => {
  392. this.state.contrlShow = false
  393. this.tmp.contrlHideTimer = null
  394. }, 2000)
  395. },
  396. toggleContrlShow () {
  397. this.state.contrlShow = !this.state.contrlShow
  398. },
  399. getTime () {
  400. this.$video.addEventListener('durationchange', (e) => {
  401. console.log(e)
  402. })
  403. this.$video.addEventListener('progress', (e) => {
  404. this.video.loaded = (-1 + (this.$video.buffered.end(0) / this.$video.duration)) * 100
  405. })
  406. this.video.len = this.$video.duration
  407. },
  408. setVideoByTime (percent) {
  409. this.$video.currentTime = Math.floor(percent * this.video.len)
  410. },
  411. play () {
  412. this.state.playing = !this.state.playing
  413. if (this.$video) {
  414. if (this.state.playing) {
  415. this.$video.play()
  416. this.mouseLeaveVideo()
  417. this.$video.addEventListener('timeupdate', this.timeline)
  418. this.$video.addEventListener('ended', (e) => {
  419. this.state.playing = false
  420. this.video.pos.current = 0
  421. this.$video.currentTime = 0
  422. })
  423. } else {
  424. this.$video.pause()
  425. }
  426. }
  427. },
  428. timeline () {
  429. const percent = this.$video.currentTime / this.$video.duration
  430. this.video.pos.current = (this.video.pos.width * percent).toFixed(3)
  431. this.video.displayTime = timeParse(this.$video.duration - this.$video.currentTime)
  432. },
  433. volMove (e) {
  434. this.initVol()
  435. this.volume.moving = true
  436. },
  437. videoMove (e) {
  438. this.initVideo()
  439. this.video.moving = true
  440. },
  441. slideClick (e) {
  442. this.videoSlideMove(e)
  443. },
  444. volSlideClick (e) {
  445. this.volSlideMove(e)
  446. },
  447. volMuted () {
  448. this.$video.muted = !this.$video.muted
  449. this.volume.muted = this.$video.muted
  450. },
  451. setVol (val) {
  452. if (this.$video) {
  453. this.volume.pos.current = val * this.volume.pos.width
  454. this.volume.percent = val * 100
  455. this.$video.volume = val
  456. }
  457. },
  458. fullScreen () {
  459. if (!this.state.fullScreen) {
  460. this.state.fullScreen = true
  461. this.$video.webkitRequestFullScreen()
  462. } else {
  463. this.state.fullScreen = false
  464. document.webkitCancelFullScreen()
  465. }
  466. setTimeout(this.initVideo, 200)
  467. },
  468. mouseMoveAction (e) {
  469. if (this.volume.moving) {
  470. this.volSlideMove(e)
  471. }
  472. if (this.video.moving) {
  473. this.videoSlideMove(e)
  474. }
  475. this.contrlHider(e)
  476. },
  477. contrlHider (e) {
  478. const x = getMousePosition(e, 'x')
  479. const y = getMousePosition(e, 'y')
  480. if (!this.player.pos) return
  481. if (x > this.player.pos.left &&
  482. x < this.player.pos.left + this.player.pos.width
  483. ) {
  484. if (
  485. y > this.player.pos.top + this.player.pos.height * 0.6 &&
  486. y < this.player.pos.top + this.player.pos.height
  487. ) {
  488. return this.mouseEnterVideo()
  489. }
  490. }
  491. return this.mouseLeaveVideo()
  492. },
  493. volSlideMove (e) {
  494. const x = getMousePosition(e) - this.volume.pos.start
  495. if (x > 0 && x < this.volume.pos.width) {
  496. this.setVol(x / this.volume.pos.width)
  497. }
  498. },
  499. videoSlideMove (e) {
  500. const x = getMousePosition(e) - this.video.pos.start
  501. if (x > 0 && x < this.video.pos.width) {
  502. this.video.pos.current = x
  503. this.setVideoByTime(x / this.video.pos.width)
  504. }
  505. },
  506. mouseUpAction (e) {
  507. this.volume.moving = false
  508. this.video.moving = false
  509. }
  510. }
  511. }
  512. </script>