├── .node-version ├── .gitignore ├── assets └── logo.png ├── circle.yml ├── nodemon.json ├── babel.config.js ├── .eslintrc ├── test ├── index.html └── src │ └── index.jsx ├── .editorconfig ├── LICENSE ├── package.json ├── css ├── modal-video.min.css └── modal-video.css ├── scss └── modal-video.scss ├── readme.md ├── src └── index.jsx └── lib └── index.js /.node-version: -------------------------------------------------------------------------------- 1 | 18.12.1 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | yarn.lock 3 | .DS_Store 4 | .idea 5 | test/dist -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appleple/react-modal-video/HEAD/assets/logo.png -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | node: 3 | version: 6.2.0 4 | dependencies: 5 | override: 6 | - "npm install" 7 | test: 8 | override: 9 | - "npm run test" -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "execMap": { 3 | "js": "node", 4 | "jsx": "jsx {{filename}} | node" 5 | }, 6 | "ext": "jsx scss", 7 | "ignore": [ 8 | "test/dist", 9 | "node_modules", 10 | "lib" 11 | ], 12 | "verbose": true 13 | } -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ['@babel/preset-env', 4 | { 5 | targets: { 6 | ie: 11, 7 | }, 8 | useBuiltIns: 'usage', 9 | corejs: 3, 10 | }, 11 | ], 12 | ['@babel/preset-react'], 13 | ] 14 | }; 15 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true 4 | }, 5 | "globals": { 6 | "document": true, 7 | "window": true 8 | }, 9 | "rules":{ 10 | "comma-dangle":0 11 | }, 12 | "parserOptions": { 13 | "sourceType": "module", 14 | "ecmaVersion": 2015, 15 | "ecmaFeatures": { 16 | "jsx": true 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | modal-video.js 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*.js] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.html] 13 | # すべてのファイルに適用する 14 | charset = utf-8 15 | # 文字コードを統一 16 | indent_style = tab 17 | #インデントを統一する。「tab」か「 space」 18 | indent_size = 2 19 | # インデントの数を統一 20 | trim_trailing_whitespace = true 21 | # 行末のホワイトスペースを削除 22 | insert_final_newline = true 23 | # フォルダの最後の行に改行 24 | end_of_line = lf 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 appleple 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test/src/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import ModalVideo from '../../lib/index'; 4 | 5 | class App extends React.Component { 6 | constructor() { 7 | super(); 8 | this.state = { 9 | isOpen: false, 10 | isOpenYouku: false, 11 | isOpenCustom: false, 12 | customUrl: 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4', 13 | }; 14 | this.openModal = this.openModal.bind(this); 15 | } 16 | 17 | openModal() { 18 | this.setState({ isOpen: true }); 19 | } 20 | 21 | render() { 22 | return ( 23 | 24 | this.setState({ isOpen: false })} 30 | /> 31 | 32 | 33 | this.setState({ isOpenVimeo: false })} 38 | /> 39 | 40 | 41 | this.setState({ isOpenYouku: false })} 46 | /> 47 | 48 | 49 | this.setState({ isOpenCustom: false })} 54 | /> 55 | 56 | 57 | ); 58 | } 59 | } 60 | 61 | ReactDOM.render(, document.getElementById('root')); 62 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-modal-video", 3 | "version": "2.0.2", 4 | "main": "lib/index.js", 5 | "description": "Modal Video Viewer", 6 | "author": "appleple", 7 | "license": "MIT", 8 | "scripts": { 9 | "test": "eslint ./src/index.jsx --fix", 10 | "build:js": "npm-run-all -p build:lib build:test", 11 | "build:lib": "npm run babel", 12 | "build:test": "browserify ./test/src/index.jsx -t babelify -o ./test/dist/index.js", 13 | "build:sass": "npm-run-all -p sass sass:min", 14 | "babel": "babel src --out-dir lib", 15 | "sass": "sass ./scss/modal-video.scss ./css/modal-video.css --style expanded --no-source-map", 16 | "sass:min": "sass ./scss/modal-video.scss ./css/modal-video.min.css --style compressed --no-source-map", 17 | "watch:js": "onchange \"src/\" -- npm run build:js", 18 | "watch:sass": "onchange \"scss\" -- npm run build:sass", 19 | "watch:test": "onchange \"test/src\" -- npm run build:test", 20 | "sync": "browser-sync start --server './' --files './test/dist/*.js' './css/*.css' --startPath '/test/index.html'", 21 | "start": "npm-run-all -p watch:js watch:sass watch:test sync", 22 | "deploy": "np --no-cleanup" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "https://github.com/appleple/react-modal-video.git" 27 | }, 28 | "devDependencies": { 29 | "@babel/cli": "^7.17.10", 30 | "@babel/core": "^7.18.5", 31 | "@babel/preset-env": "^7.20.2", 32 | "@babel/preset-react": "^7.18.6", 33 | "babelify": "^10.0.0", 34 | "browser-sync": "^2.27.10", 35 | "browserify": "^17.0.0", 36 | "eslint": "^8.17.0", 37 | "eslint-config-airbnb": "^19.0.4", 38 | "eslint-config-airbnb-base": "^15.0.0", 39 | "eslint-plugin-import": "^2.26.0", 40 | "npm-run-all": "^4.1.5", 41 | "sass": "^1.52.3", 42 | "onchange": "^7.1.0", 43 | "np": "^7.6.1" 44 | }, 45 | "dependencies": { 46 | "core-js": "^3.27.2", 47 | "react-transition-group": "^4.4.2" 48 | }, 49 | "peerDependencies": { 50 | "react": "^17.0.0 || ^18.2.0", 51 | "react-dom": "^17.0.0 || ^18.2.0" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /css/modal-video.min.css: -------------------------------------------------------------------------------- 1 | @keyframes modal-video{from{opacity:0}to{opacity:1}}@keyframes modal-video-inner{from{transform:translate(0, 100px)}to{transform:translate(0, 0)}}.modal-video{position:fixed;top:0;left:0;width:100%;height:100%;background-color:rgba(0,0,0,.5);z-index:1000000;cursor:pointer;opacity:1;animation-timing-function:ease-out;animation-duration:.3s;animation-name:modal-video;-webkit-transition:opacity .3s ease-out;-moz-transition:opacity .3s ease-out;-ms-transition:opacity .3s ease-out;-o-transition:opacity .3s ease-out;transition:opacity .3s ease-out}.modal-video-effect-exit{opacity:0}.modal-video-effect-exit .modal-video-movie-wrap{-webkit-transform:translate(0, 100px);-moz-transform:translate(0, 100px);-ms-transform:translate(0, 100px);-o-transform:translate(0, 100px);transform:translate(0, 100px)}.modal-video-body{max-width:960px;width:100%;height:100%;margin:0 auto;padding:0 10px;display:flex;justify-content:center;box-sizing:border-box}.modal-video-inner{display:flex;justify-content:center;align-items:center;width:100%;height:100%}@media(orientation: landscape){.modal-video-inner{padding:10px 60px;box-sizing:border-box}}.modal-video-movie-wrap{width:100%;height:0;position:relative;padding-bottom:56.25%;background-color:#333;animation-timing-function:ease-out;animation-duration:.3s;animation-name:modal-video-inner;-webkit-transform:translate(0, 0);-moz-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0);-webkit-transition:-webkit-transform .3s ease-out;-moz-transition:-moz-transform .3s ease-out;-ms-transition:-ms-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out}.modal-video-movie-wrap iframe{position:absolute;top:0;left:0;width:100%;height:100%}.modal-video-close-btn{position:absolute;z-index:2;top:-45px;right:0px;display:inline-block;width:35px;height:35px;overflow:hidden;border:none;background:rgba(0,0,0,0)}@media(orientation: landscape){.modal-video-close-btn{top:0;right:-45px}}.modal-video-close-btn:before{transform:rotate(45deg)}.modal-video-close-btn:after{transform:rotate(-45deg)}.modal-video-close-btn:before,.modal-video-close-btn:after{content:"";position:absolute;height:2px;width:100%;top:50%;left:0;margin-top:-1px;background:#fff;border-radius:5px;margin-top:-6px} 2 | -------------------------------------------------------------------------------- /css/modal-video.css: -------------------------------------------------------------------------------- 1 | @keyframes modal-video { 2 | from { 3 | opacity: 0; 4 | } 5 | to { 6 | opacity: 1; 7 | } 8 | } 9 | @keyframes modal-video-inner { 10 | from { 11 | transform: translate(0, 100px); 12 | } 13 | to { 14 | transform: translate(0, 0); 15 | } 16 | } 17 | .modal-video { 18 | position: fixed; 19 | top: 0; 20 | left: 0; 21 | width: 100%; 22 | height: 100%; 23 | background-color: rgba(0, 0, 0, 0.5); 24 | z-index: 1000000; 25 | cursor: pointer; 26 | opacity: 1; 27 | animation-timing-function: ease-out; 28 | animation-duration: 0.3s; 29 | animation-name: modal-video; 30 | -webkit-transition: opacity 0.3s ease-out; 31 | -moz-transition: opacity 0.3s ease-out; 32 | -ms-transition: opacity 0.3s ease-out; 33 | -o-transition: opacity 0.3s ease-out; 34 | transition: opacity 0.3s ease-out; 35 | } 36 | 37 | .modal-video-effect-exit { 38 | opacity: 0; 39 | } 40 | .modal-video-effect-exit .modal-video-movie-wrap { 41 | -webkit-transform: translate(0, 100px); 42 | -moz-transform: translate(0, 100px); 43 | -ms-transform: translate(0, 100px); 44 | -o-transform: translate(0, 100px); 45 | transform: translate(0, 100px); 46 | } 47 | 48 | .modal-video-body { 49 | max-width: 960px; 50 | width: 100%; 51 | height: 100%; 52 | margin: 0 auto; 53 | padding: 0 10px; 54 | display: flex; 55 | justify-content: center; 56 | box-sizing: border-box; 57 | } 58 | 59 | .modal-video-inner { 60 | display: flex; 61 | justify-content: center; 62 | align-items: center; 63 | width: 100%; 64 | height: 100%; 65 | } 66 | @media (orientation: landscape) { 67 | .modal-video-inner { 68 | padding: 10px 60px; 69 | box-sizing: border-box; 70 | } 71 | } 72 | 73 | .modal-video-movie-wrap { 74 | width: 100%; 75 | height: 0; 76 | position: relative; 77 | padding-bottom: 56.25%; 78 | background-color: #333; 79 | animation-timing-function: ease-out; 80 | animation-duration: 0.3s; 81 | animation-name: modal-video-inner; 82 | -webkit-transform: translate(0, 0); 83 | -moz-transform: translate(0, 0); 84 | -ms-transform: translate(0, 0); 85 | -o-transform: translate(0, 0); 86 | transform: translate(0, 0); 87 | -webkit-transition: -webkit-transform 0.3s ease-out; 88 | -moz-transition: -moz-transform 0.3s ease-out; 89 | -ms-transition: -ms-transform 0.3s ease-out; 90 | -o-transition: -o-transform 0.3s ease-out; 91 | transition: transform 0.3s ease-out; 92 | } 93 | .modal-video-movie-wrap iframe { 94 | position: absolute; 95 | top: 0; 96 | left: 0; 97 | width: 100%; 98 | height: 100%; 99 | } 100 | 101 | .modal-video-close-btn { 102 | position: absolute; 103 | z-index: 2; 104 | top: -45px; 105 | right: 0px; 106 | display: inline-block; 107 | width: 35px; 108 | height: 35px; 109 | overflow: hidden; 110 | border: none; 111 | background: transparent; 112 | } 113 | @media (orientation: landscape) { 114 | .modal-video-close-btn { 115 | top: 0; 116 | right: -45px; 117 | } 118 | } 119 | .modal-video-close-btn:before { 120 | transform: rotate(45deg); 121 | } 122 | .modal-video-close-btn:after { 123 | transform: rotate(-45deg); 124 | } 125 | .modal-video-close-btn:before, .modal-video-close-btn:after { 126 | content: ""; 127 | position: absolute; 128 | height: 2px; 129 | width: 100%; 130 | top: 50%; 131 | left: 0; 132 | margin-top: -1px; 133 | background: #fff; 134 | border-radius: 5px; 135 | margin-top: -6px; 136 | } 137 | -------------------------------------------------------------------------------- /scss/modal-video.scss: -------------------------------------------------------------------------------- 1 | $animation-speed: .3s; 2 | $animation-function: ease-out; 3 | $backdrop-color: rgba(0, 0, 0, .5); 4 | 5 | @keyframes modal-video { 6 | from { 7 | opacity: 0; 8 | } 9 | 10 | to { 11 | opacity: 1; 12 | } 13 | } 14 | 15 | @keyframes modal-video-inner { 16 | from { 17 | transform: translate(0, 100px); 18 | } 19 | 20 | to { 21 | transform: translate(0, 0); 22 | } 23 | } 24 | 25 | .modal-video { 26 | position: fixed; 27 | top: 0; 28 | left: 0; 29 | width: 100%; 30 | height: 100%; 31 | background-color: $backdrop-color; 32 | z-index: 1000000; 33 | cursor: pointer; 34 | opacity: 1; 35 | animation-timing-function: $animation-function; 36 | animation-duration: $animation-speed; 37 | animation-name: modal-video; 38 | -webkit-transition: opacity $animation-speed $animation-function; 39 | -moz-transition: opacity $animation-speed $animation-function; 40 | -ms-transition: opacity $animation-speed $animation-function; 41 | -o-transition: opacity $animation-speed $animation-function; 42 | transition: opacity $animation-speed $animation-function; 43 | } 44 | 45 | .modal-video-effect-exit { 46 | opacity: 0; 47 | 48 | & .modal-video-movie-wrap { 49 | -webkit-transform: translate(0, 100px); 50 | -moz-transform: translate(0, 100px); 51 | -ms-transform: translate(0, 100px); 52 | -o-transform: translate(0, 100px); 53 | transform: translate(0, 100px); 54 | } 55 | } 56 | 57 | .modal-video-body { 58 | max-width: 960px; 59 | width: 100%; 60 | height: 100%; 61 | margin: 0 auto; 62 | padding: 0 10px; 63 | display: flex; 64 | justify-content: center; 65 | box-sizing: border-box; 66 | } 67 | 68 | .modal-video-inner { 69 | display: flex; 70 | justify-content: center; 71 | align-items: center; 72 | width: 100%; 73 | height: 100%; 74 | 75 | @media (orientation: landscape) { 76 | padding: 10px 60px; 77 | box-sizing: border-box; 78 | } 79 | } 80 | 81 | .modal-video-movie-wrap { 82 | width: 100%; 83 | height: 0; 84 | position: relative; 85 | padding-bottom: 56.25%; 86 | background-color: #333; 87 | animation-timing-function: $animation-function; 88 | animation-duration: $animation-speed; 89 | animation-name: modal-video-inner; 90 | -webkit-transform: translate(0, 0); 91 | -moz-transform: translate(0, 0); 92 | -ms-transform: translate(0, 0); 93 | -o-transform: translate(0, 0); 94 | transform: translate(0, 0); 95 | -webkit-transition: -webkit-transform $animation-speed $animation-function; 96 | -moz-transition: -moz-transform $animation-speed $animation-function; 97 | -ms-transition: -ms-transform $animation-speed $animation-function; 98 | -o-transition: -o-transform $animation-speed $animation-function; 99 | transition: transform $animation-speed $animation-function; 100 | 101 | & iframe { 102 | position: absolute; 103 | top: 0; 104 | left: 0; 105 | width: 100%; 106 | height: 100%; 107 | } 108 | } 109 | 110 | .modal-video-close-btn { 111 | position: absolute; 112 | z-index: 2; 113 | top: -45px; 114 | right: 0px; 115 | display: inline-block; 116 | width: 35px; 117 | height: 35px; 118 | overflow: hidden; 119 | border: none; 120 | background: transparent; 121 | 122 | @media (orientation: landscape) { 123 | top: 0; 124 | right: -45px; 125 | } 126 | 127 | &:before { 128 | transform: rotate(45deg); 129 | } 130 | 131 | &:after { 132 | transform: rotate(-45deg); 133 | } 134 | 135 | &:before, 136 | &:after { 137 | content: ''; 138 | position: absolute; 139 | height: 2px; 140 | width: 100%; 141 | top: 50%; 142 | left: 0; 143 | margin-top: -1px; 144 | background: #fff; 145 | border-radius: 5px; 146 | margin-top: -6px; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # react-modal-video 2 | 3 | React Modal Video Component 4 | 5 | ## Features 6 | 7 | - Not affected by dom structure. 8 | - Beautiful transition 9 | - Accessible for keyboard navigation and screen readers. 10 | - Rich options for youtube API and Vimeo API 11 | 12 | ## Demo 13 | 14 | [https://unpkg.com/react-modal-video@latest/test/index.html](https://unpkg.com/react-modal-video@latest/test/index.html) 15 | 16 | ## Install 17 | 18 | ### npm 19 | 20 | ```sh 21 | npm install react-modal-video 22 | ``` 23 | 24 | ## Usage 25 | 26 | import sass file to your project 27 | 28 | ```scss 29 | @import 'node_modules/react-modal-video/scss/modal-video.scss'; 30 | ``` 31 | 32 | ### Functional Implementation with Hooks 33 | 34 | ```jsx 35 | import React, { useState } from 'react'; 36 | import ReactDOM from 'react-dom'; 37 | import ModalVideo from 'react-modal-video'; 38 | 39 | const App = () => { 40 | const [isOpen, setOpen] = useState(false); 41 | 42 | return ( 43 | 44 | setOpen(false)} 50 | /> 51 | 54 | 55 | ); 56 | }; 57 | 58 | ReactDOM.render(, document.getElementById('root')); 59 | ``` 60 | 61 | ### Class Implementation 62 | 63 | change "isOpen" property to open and close the modal-video 64 | 65 | ```jsx 66 | import React from 'react'; 67 | import ReactDOM from 'react-dom'; 68 | import ModalVideo from 'react-modal-video'; 69 | 70 | class App extends React.Component { 71 | constructor() { 72 | super(); 73 | this.state = { 74 | isOpen: false, 75 | }; 76 | this.openModal = this.openModal.bind(this); 77 | } 78 | 79 | openModal() { 80 | this.setState({ isOpen: true }); 81 | } 82 | 83 | render() { 84 | return ( 85 | 86 | this.setState({ isOpen: false })} 91 | /> 92 | 93 | 94 | ); 95 | } 96 | } 97 | 98 | ReactDOM.render(, document.getElementById('root')); 99 | ``` 100 | 101 | ## Options 102 | 103 | - About YouTube options, please refer to https://developers.google.com/youtube/player_parameters?hl=en 104 | - About Vimeo options, please refer to https://developer.vimeo.com/apis/oembed 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 |
propertiesdefault
channel'youtube'
youtubeautoplay1
cc_load_policy1
colornull
controls1
disablekb0
enablejsapi0
endnull
fs1
h1null
iv_load_policy1
listnull
listTypenull
loop0
modestbrandingnull
originnull
playlistnull
playsinlinenull
rel0
showinfo1
start0
wmode'transparent'
theme'dark'
mute0
vimeoapifalse
autopausetrue
autoplaytrue
bylinetrue
callbacknull
colornull
heightnull
loopfalse
maxheightnull
maxwidthnull
player_idnull
portraittrue
titletrue
widthnull
xhtmlfalse
youkuautoplay1
show_related0
customurlMP4 URL / iframe URL
ratio'16:9'
allowFullScreentrue
animationSpeed300
classNamesmodalVideo'modal-video'
modalVideoClose'modal-video-close'
modalVideoBody'modal-video-body'
modalVideoInner'modal-video-inner'
modalVideoIframeWrap'modal-video-movie-wrap'
modalVideoCloseBtn'modal-video-close-btn'
ariaopenMessage'You just opened the modal video'
dismissBtnMessage'Close the modal by clicking here'
330 | 331 | ## FAQ 332 | 333 | ### How to track YouTube videos playing in modal-video by GA4? 334 | 335 | 1. Enable JS API. Turn `enablejsapi` property to `1`. 336 | 2. Load YouTube Iframe API. Add `` in HTML file. 337 | 338 | ## Licence 339 | 340 | [MIT](https://github.com/appleple/modal-video.js/blob/master/LICENSE) 341 | -------------------------------------------------------------------------------- /src/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import CSSTransition from 'react-transition-group/CSSTransition'; 3 | 4 | export default class ModalVideo extends React.Component { 5 | constructor(props) { 6 | super(props); 7 | this.state = { 8 | isOpen: false, 9 | modalVideoWidth: '100%' 10 | }; 11 | this.closeModal = this.closeModal.bind(this); 12 | this.updateFocus = this.updateFocus.bind(this); 13 | 14 | this.timeout; // used for resizing video. 15 | } 16 | 17 | openModal() { 18 | this.setState({ isOpen: true }); 19 | } 20 | 21 | closeModal() { 22 | this.setState({ isOpen: false }); 23 | if (typeof this.props.onClose === 'function') { 24 | this.props.onClose(); 25 | } 26 | } 27 | 28 | keydownHandler(e) { 29 | if (e.keyCode === 27) { 30 | this.closeModal(); 31 | } 32 | } 33 | 34 | componentDidMount() { 35 | document.addEventListener('keydown', this.keydownHandler.bind(this)); 36 | window.addEventListener('resize', this.resizeModalVideoWhenHeightGreaterThanWindowHeight.bind(this)); 37 | this.setState({ 38 | modalVideoWidth: this.getWidthFulfillAspectRatio(this.props.ratio, window.innerHeight, window.innerWidth) 39 | }); 40 | } 41 | 42 | componentWillUnmount() { 43 | document.removeEventListener('keydown', this.keydownHandler.bind(this)); 44 | window.removeEventListener('resize', this.resizeModalVideoWhenHeightGreaterThanWindowHeight.bind(this)); 45 | } 46 | 47 | static getDerivedStateFromProps(props) { 48 | return { isOpen: props.isOpen }; 49 | } 50 | 51 | componentDidUpdate() { 52 | if (this.state.isOpen && this.modal) { 53 | this.modal.focus(); 54 | } 55 | } 56 | 57 | updateFocus(e) { 58 | if (this.state.isOpen) { 59 | e.preventDefault(); 60 | e.stopPropagation(); 61 | 62 | if (e.keyCode === 9) { 63 | if (this.modal === document.activeElement) { 64 | this.modaliflame.focus(); 65 | } 66 | else if (this.modalbtn === document.activeElement) { 67 | this.modal.focus(); 68 | } 69 | } 70 | } 71 | } 72 | 73 | /** 74 | * Resize modal-video-iframe-wrap when window size changed when the height of the video is greater than the height of the window. 75 | */ 76 | resizeModalVideoWhenHeightGreaterThanWindowHeight() { 77 | clearTimeout(this.timeout); 78 | 79 | this.timeout = setTimeout(() => { 80 | const width = this.getWidthFulfillAspectRatio(this.props.ratio, window.innerHeight, window.innerWidth); 81 | if (this.state.modalVideoWidth != width) { 82 | this.setState({ 83 | modalVideoWidth: width 84 | }); 85 | } 86 | }, 10); 87 | } 88 | 89 | getQueryString(obj) { 90 | let url = ''; 91 | for (const key in obj) { 92 | if (obj.hasOwnProperty(key)) { 93 | if (obj[key] !== null) { 94 | url += `${key}=${obj[key]}&`; 95 | } 96 | } 97 | } 98 | return url.substr(0, url.length - 1); 99 | } 100 | 101 | getYoutubeUrl(youtube, videoId) { 102 | const query = this.getQueryString(youtube); 103 | return `//www.youtube.com/embed/${videoId}?${query}`; 104 | } 105 | 106 | getVimeoUrl(vimeo, videoId) { 107 | const query = this.getQueryString(vimeo); 108 | return `//player.vimeo.com/video/${videoId}?${query}`; 109 | } 110 | 111 | getYoukuUrl(youku, videoId) { 112 | const query = this.getQueryString(youku); 113 | return `//player.youku.com/embed/${videoId}?${query}`; 114 | } 115 | 116 | getVideoUrl(opt, videoId) { 117 | if (opt.channel === 'youtube') { 118 | return this.getYoutubeUrl(opt.youtube, videoId); 119 | } if (opt.channel === 'vimeo') { 120 | return this.getVimeoUrl(opt.vimeo, videoId); 121 | } if (opt.channel === 'youku') { 122 | return this.getYoukuUrl(opt.youku, videoId); 123 | } if (opt.channel === 'custom') { 124 | return opt.url; 125 | } 126 | } 127 | 128 | getPadding(ratio) { 129 | const arr = ratio.split(':'); 130 | const width = Number(arr[0]); 131 | const height = Number(arr[1]); 132 | const padding = height * 100 / width; 133 | return `${padding}%`; 134 | } 135 | 136 | /** 137 | * Calculate the width of the video fulfill aspect ratio. 138 | * When the height of the video is greater than the height of the window, 139 | * this function return the width that fulfill the aspect ratio for the height of the window. 140 | * In other cases, this function return '100%'(the height relative to the width is determined by css). 141 | * 142 | * @param string ratio 143 | * @param number maxWidth 144 | * @returns number | '100%' 145 | */ 146 | getWidthFulfillAspectRatio(ratio, maxHeight, maxWidth) { 147 | const arr = ratio.split(':'); 148 | const width = Number(arr[0]); 149 | const height = Number(arr[1]); 150 | 151 | // Height that fulfill the aspect ratio for maxWidth. 152 | const videoHeight = maxWidth * (height / width); 153 | 154 | if (maxHeight < videoHeight) { 155 | // when the height of the video is greater than the height of the window. 156 | // calculate the width that fulfill the aspect ratio for the height of the window. 157 | 158 | // ex: 16:9 aspect ratio 159 | // 16:9 = width : height 160 | // → width = 16 / 9 * height 161 | return Math.floor(width / height * maxHeight); 162 | } 163 | 164 | return '100%'; 165 | } 166 | 167 | render() { 168 | const modalVideoInnerStyle = { 169 | width: this.state.modalVideoWidth 170 | }; 171 | 172 | const modalVideoIframeWrapStyle = { 173 | paddingBottom: this.getPadding(this.props.ratio) 174 | }; 175 | 176 | return ( 177 | 181 | {() => { 182 | if (!this.state.isOpen) { 183 | return null; 184 | } 185 | 186 | return ( 187 |
{this.modal = node; }} onKeyDown={this.updateFocus}> 189 |
190 |
191 |
192 | { 193 | this.props.children 194 | ||