├── test ├── actions │ └── .keep ├── sources │ └── .keep ├── stores │ └── .keep ├── loadtests.js ├── config │ └── ConfigTest.js ├── components │ └── MainTest.js └── helpers │ └── shallowRenderHelper.js ├── .npminstall.done ├── .yo-rc.json ├── .DS_Store ├── src ├── icons.png ├── favicon.ico ├── images │ ├── 1.jpg │ ├── 10.jpg │ ├── 11.jpg │ ├── 12.jpg │ ├── 13.jpg │ ├── 14.jpg │ ├── 15.jpg │ ├── 16.jpg │ ├── 2.jpg │ ├── 3.jpg │ ├── 4.jpg │ ├── 5.jpg │ ├── 6.jpg │ ├── 7.jpg │ ├── 8.jpg │ └── 9.jpg ├── fonts │ └── icons │ │ ├── iconfont.eot │ │ ├── iconfont.ttf │ │ ├── iconfont.woff │ │ └── iconfont.svg ├── components │ ├── utils │ │ └── util.js │ ├── music │ │ ├── progress.less │ │ ├── progress.js │ │ ├── player.less │ │ └── player.js │ ├── photo │ │ ├── ControllerUnit.js │ │ └── ImgFigure.js │ └── Main.js ├── config │ ├── base.js │ ├── test.js │ ├── dev.js │ ├── dist.js │ └── README.md ├── index.js ├── stores │ └── README.md ├── sources │ └── README.md ├── actions │ └── README.md ├── index.html ├── data │ ├── musicDatas.js │ └── imageDatas.json └── styles │ ├── App.scss │ └── common.css ├── dist ├── favicon.ico ├── assets │ ├── 086dfb06e2ebdaded1f7d44b5c5dbc04.jpg │ ├── 1700e1ec1ceed809a3f22adeb131f8fb.jpg │ ├── 3e5e5264e7256b8ac0ce18686b101ef7.jpg │ ├── 6d764766ad0c954cf316d145995e9f71.jpg │ ├── a8673d0af258a0f0b3b4b93574927bed.jpg │ ├── ae6343f3f4a7daf7a0c65c7058e71886.jpg │ ├── b1d216eb751f93e7df1620a58aa47c1c.jpg │ ├── bcf71587cf413cdda9bd73210a2b6902.jpg │ ├── c0198ef11b44fbfc99547ed87deb6d6d.jpg │ ├── c4cf123abec40a17539eaaf80c797ece.jpg │ ├── cd705b3b9ae93e59aa1af0a33423dbb4.jpg │ ├── d5ba34a07c0fe9918213ec9ed856ef89.jpg │ ├── dfd764b24ca4d1db08ee1c934d139304.jpg │ ├── e449cd3c5a7633c4b06d7d35ba7a309a.jpg │ ├── e5eb85c63257929113f8aeb2e96be780.jpg │ ├── f3beaa5015c521e8e49687936ba7e03d.jpg │ └── 47057c894e3bb6696a34ddfc5e9a9387.svg ├── README.md ├── static │ └── README.md └── index.html ├── .babelrc ├── .editorconfig ├── .gitignore ├── .eslintrc ├── karma.conf.js ├── webpack.config.js ├── LICENSE ├── cfg ├── dev.js ├── dist.js ├── base.js ├── test.js └── defaults.js ├── server.js ├── package.json └── README.md /test/actions/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/sources/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stores/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.npminstall.done: -------------------------------------------------------------------------------- 1 | Sun Feb 04 2018 19:23:08 GMT+0800 (CST) -------------------------------------------------------------------------------- /.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-react-webpack": {} 3 | } -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnngu/MusicPhoto/HEAD/.DS_Store -------------------------------------------------------------------------------- /src/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnngu/MusicPhoto/HEAD/src/icons.png -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnngu/MusicPhoto/HEAD/src/favicon.ico -------------------------------------------------------------------------------- /dist/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnngu/MusicPhoto/HEAD/dist/favicon.ico -------------------------------------------------------------------------------- /src/images/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnngu/MusicPhoto/HEAD/src/images/1.jpg -------------------------------------------------------------------------------- /src/images/10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnngu/MusicPhoto/HEAD/src/images/10.jpg -------------------------------------------------------------------------------- /src/images/11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnngu/MusicPhoto/HEAD/src/images/11.jpg -------------------------------------------------------------------------------- /src/images/12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnngu/MusicPhoto/HEAD/src/images/12.jpg -------------------------------------------------------------------------------- /src/images/13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnngu/MusicPhoto/HEAD/src/images/13.jpg -------------------------------------------------------------------------------- /src/images/14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnngu/MusicPhoto/HEAD/src/images/14.jpg -------------------------------------------------------------------------------- /src/images/15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnngu/MusicPhoto/HEAD/src/images/15.jpg -------------------------------------------------------------------------------- /src/images/16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnngu/MusicPhoto/HEAD/src/images/16.jpg -------------------------------------------------------------------------------- /src/images/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnngu/MusicPhoto/HEAD/src/images/2.jpg -------------------------------------------------------------------------------- /src/images/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnngu/MusicPhoto/HEAD/src/images/3.jpg -------------------------------------------------------------------------------- /src/images/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnngu/MusicPhoto/HEAD/src/images/4.jpg -------------------------------------------------------------------------------- /src/images/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnngu/MusicPhoto/HEAD/src/images/5.jpg -------------------------------------------------------------------------------- /src/images/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnngu/MusicPhoto/HEAD/src/images/6.jpg -------------------------------------------------------------------------------- /src/images/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnngu/MusicPhoto/HEAD/src/images/7.jpg -------------------------------------------------------------------------------- /src/images/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnngu/MusicPhoto/HEAD/src/images/8.jpg -------------------------------------------------------------------------------- /src/images/9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnngu/MusicPhoto/HEAD/src/images/9.jpg -------------------------------------------------------------------------------- /src/fonts/icons/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnngu/MusicPhoto/HEAD/src/fonts/icons/iconfont.eot -------------------------------------------------------------------------------- /src/fonts/icons/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnngu/MusicPhoto/HEAD/src/fonts/icons/iconfont.ttf -------------------------------------------------------------------------------- /src/fonts/icons/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnngu/MusicPhoto/HEAD/src/fonts/icons/iconfont.woff -------------------------------------------------------------------------------- /src/components/utils/util.js: -------------------------------------------------------------------------------- 1 | export function randomRange(under, over) { 2 | return Math.ceil(Math.random() * (over - under) + under); 3 | } -------------------------------------------------------------------------------- /dist/assets/086dfb06e2ebdaded1f7d44b5c5dbc04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnngu/MusicPhoto/HEAD/dist/assets/086dfb06e2ebdaded1f7d44b5c5dbc04.jpg -------------------------------------------------------------------------------- /dist/assets/1700e1ec1ceed809a3f22adeb131f8fb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnngu/MusicPhoto/HEAD/dist/assets/1700e1ec1ceed809a3f22adeb131f8fb.jpg -------------------------------------------------------------------------------- /dist/assets/3e5e5264e7256b8ac0ce18686b101ef7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnngu/MusicPhoto/HEAD/dist/assets/3e5e5264e7256b8ac0ce18686b101ef7.jpg -------------------------------------------------------------------------------- /dist/assets/6d764766ad0c954cf316d145995e9f71.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnngu/MusicPhoto/HEAD/dist/assets/6d764766ad0c954cf316d145995e9f71.jpg -------------------------------------------------------------------------------- /dist/assets/a8673d0af258a0f0b3b4b93574927bed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnngu/MusicPhoto/HEAD/dist/assets/a8673d0af258a0f0b3b4b93574927bed.jpg -------------------------------------------------------------------------------- /dist/assets/ae6343f3f4a7daf7a0c65c7058e71886.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnngu/MusicPhoto/HEAD/dist/assets/ae6343f3f4a7daf7a0c65c7058e71886.jpg -------------------------------------------------------------------------------- /dist/assets/b1d216eb751f93e7df1620a58aa47c1c.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnngu/MusicPhoto/HEAD/dist/assets/b1d216eb751f93e7df1620a58aa47c1c.jpg -------------------------------------------------------------------------------- /dist/assets/bcf71587cf413cdda9bd73210a2b6902.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnngu/MusicPhoto/HEAD/dist/assets/bcf71587cf413cdda9bd73210a2b6902.jpg -------------------------------------------------------------------------------- /dist/assets/c0198ef11b44fbfc99547ed87deb6d6d.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnngu/MusicPhoto/HEAD/dist/assets/c0198ef11b44fbfc99547ed87deb6d6d.jpg -------------------------------------------------------------------------------- /dist/assets/c4cf123abec40a17539eaaf80c797ece.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnngu/MusicPhoto/HEAD/dist/assets/c4cf123abec40a17539eaaf80c797ece.jpg -------------------------------------------------------------------------------- /dist/assets/cd705b3b9ae93e59aa1af0a33423dbb4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnngu/MusicPhoto/HEAD/dist/assets/cd705b3b9ae93e59aa1af0a33423dbb4.jpg -------------------------------------------------------------------------------- /dist/assets/d5ba34a07c0fe9918213ec9ed856ef89.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnngu/MusicPhoto/HEAD/dist/assets/d5ba34a07c0fe9918213ec9ed856ef89.jpg -------------------------------------------------------------------------------- /dist/assets/dfd764b24ca4d1db08ee1c934d139304.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnngu/MusicPhoto/HEAD/dist/assets/dfd764b24ca4d1db08ee1c934d139304.jpg -------------------------------------------------------------------------------- /dist/assets/e449cd3c5a7633c4b06d7d35ba7a309a.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnngu/MusicPhoto/HEAD/dist/assets/e449cd3c5a7633c4b06d7d35ba7a309a.jpg -------------------------------------------------------------------------------- /dist/assets/e5eb85c63257929113f8aeb2e96be780.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnngu/MusicPhoto/HEAD/dist/assets/e5eb85c63257929113f8aeb2e96be780.jpg -------------------------------------------------------------------------------- /dist/assets/f3beaa5015c521e8e49687936ba7e03d.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nnngu/MusicPhoto/HEAD/dist/assets/f3beaa5015c521e8e49687936ba7e03d.jpg -------------------------------------------------------------------------------- /src/config/base.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Settings configured here will be merged into the final config object. 4 | export default { 5 | } 6 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "stage-0", 5 | "react" 6 | ], 7 | 8 | "plugins": ["transform-decorators-legacy"] 9 | } 10 | 11 | -------------------------------------------------------------------------------- /dist/README.md: -------------------------------------------------------------------------------- 1 | # About the dist folder 2 | After building the dist version of your project, the generated files are stored in this folder. You should keep it under version control. 3 | -------------------------------------------------------------------------------- /src/config/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import baseConfig from './base'; 4 | 5 | let config = { 6 | appEnv: 'test' // don't remove the appEnv property here 7 | }; 8 | 9 | export default Object.freeze(Object.assign(baseConfig, config)); 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /dist/static/README.md: -------------------------------------------------------------------------------- 1 | # static 2 | 3 | Files and directories that you put in `static` will be copied to the 4 | `dist/static` directory during the build step. Use it to provide 5 | arbitrary static assets that can be referenced by path in your 6 | application. 7 | -------------------------------------------------------------------------------- /src/config/dev.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import baseConfig from './base'; 4 | 5 | let config = { 6 | appEnv: 'dev' // feel free to remove the appEnv property here 7 | }; 8 | 9 | export default Object.freeze(Object.assign({}, baseConfig, config)); 10 | -------------------------------------------------------------------------------- /src/config/dist.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import baseConfig from './base'; 4 | 5 | let config = { 6 | appEnv: 'dist' // feel free to remove the appEnv property here 7 | }; 8 | 9 | export default Object.freeze(Object.assign({}, baseConfig, config)); 10 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import 'core-js/fn/object/assign'; 2 | import React from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | import App from './components/Main'; 5 | 6 | // Render the main component into the dom 7 | ReactDOM.render(, document.getElementById('content')); 8 | -------------------------------------------------------------------------------- /test/loadtests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('babel-polyfill'); 4 | require('core-js/fn/object/assign'); 5 | 6 | // Add support for all files in the test directory 7 | const testsContext = require.context('.', true, /(Test\.js$)|(Helper\.js$)/); 8 | testsContext.keys().forEach(testsContext); 9 | -------------------------------------------------------------------------------- /src/components/music/progress.less: -------------------------------------------------------------------------------- 1 | .components-progress { 2 | display: inline-block; 3 | width: 100%; 4 | height: 3px; 5 | position: relative; 6 | background: #aaa; 7 | cursor: pointer; 8 | 9 | .progress { 10 | width: 0%; 11 | height: 3px; 12 | left: 0; 13 | top: 0; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/config/ConfigTest.js: -------------------------------------------------------------------------------- 1 | /*eslint-env node, mocha */ 2 | /*global expect */ 3 | /*eslint no-console: 0*/ 4 | 'use strict'; 5 | 6 | import config from 'config'; 7 | 8 | describe('appEnvConfigTests', function () { 9 | it('should load app config file depending on current --env', function () { 10 | expect(config.appEnv).to.equal('test'); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/stores/README.md: -------------------------------------------------------------------------------- 1 | # About this folder 2 | This folder will hold all of your **flux** stores. 3 | You can include them into your components like this: 4 | 5 | ```javascript 6 | let react = require('react/addons'); 7 | let MyStore = require('stores/MyStore'); 8 | class MyComponent extends React.Component { 9 | constructor(props) { 10 | super(props); 11 | MyStore.doSomething(); 12 | } 13 | } 14 | ``` 15 | -------------------------------------------------------------------------------- /src/sources/README.md: -------------------------------------------------------------------------------- 1 | # About this folder 2 | This folder will hold all of your **flux** datasources. 3 | You can include them into your components or stores like this: 4 | 5 | ```javascript 6 | let react = require('react/addons'); 7 | let MySource = require('sources/MyAction'); 8 | class MyComponent extends React.Component { 9 | constructor(props) { 10 | super(props); 11 | MySource.getRemoteData(); 12 | } 13 | } 14 | ``` 15 | -------------------------------------------------------------------------------- /src/actions/README.md: -------------------------------------------------------------------------------- 1 | # About this folder 2 | This folder will hold all of your **flux** actions if you are using flux. 3 | You can include actions into your components or stores like this: 4 | 5 | ```javascript 6 | let react = require('react/addons'); 7 | let MyAction = require('actions/MyAction'); 8 | class MyComponent extends React.Component { 9 | constructor(props) { 10 | super(props); 11 | MyAction.exampleMethod(); 12 | } 13 | } 14 | ``` 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | 24 | # Dependency directory 25 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 26 | node_modules 27 | 28 | # Bower 29 | bower_components/ 30 | 31 | # IDE/Editor data 32 | .idea 33 | -------------------------------------------------------------------------------- /test/components/MainTest.js: -------------------------------------------------------------------------------- 1 | /*eslint-env node, mocha */ 2 | /*global expect */ 3 | /*eslint no-console: 0*/ 4 | 'use strict'; 5 | 6 | // Uncomment the following lines to use the react test utilities 7 | // import React from 'react/addons'; 8 | // const TestUtils = React.addons.TestUtils; 9 | import createComponent from 'helpers/shallowRenderHelper'; 10 | 11 | import Main from 'components/Main'; 12 | 13 | describe('MainComponent', function () { 14 | 15 | beforeEach(function () { 16 | this.MainComponent = createComponent(Main); 17 | }); 18 | 19 | it('should have its component name as default className', function () { 20 | expect(this.MainComponent.props.className).to.equal('index'); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "plugins": [ 4 | "react" 5 | ], 6 | "parserOptions": { 7 | "ecmaVersion": 6, 8 | "sourceType": "module", 9 | "ecmaFeatures": { 10 | "jsx": true 11 | } 12 | }, 13 | "env": { 14 | "browser": true, 15 | "amd": true, 16 | "es6": true, 17 | "node": true, 18 | "mocha": true 19 | }, 20 | "rules": { 21 | "comma-dangle": 1, 22 | "quotes": [ 1, "single" ], 23 | "no-undef": 1, 24 | "global-strict": 0, 25 | "no-extra-semi": 1, 26 | "no-underscore-dangle": 0, 27 | "no-console": 1, 28 | "no-unused-vars": 1, 29 | "no-trailing-spaces": [1, { "skipBlankLines": true }], 30 | "no-unreachable": 1, 31 | "no-alert": 0, 32 | "react/jsx-uses-react": 1, 33 | "react/jsx-uses-vars": 1 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/components/music/progress.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | require('./progress.less'); 4 | 5 | let Progress = React.createClass({ 6 | 7 | getDefaultProps() { 8 | return { 9 | barColor: '#2f9842' 10 | } 11 | }, 12 | 13 | changeProgress(e) { 14 | let progressBar = this.refs.progressBar; 15 | let progress = (e.clientX - progressBar.getBoundingClientRect().left) / progressBar.clientWidth; 16 | this.props.onProgressChange && this.props.onProgressChange(progress); 17 | }, 18 | 19 | render() { 20 | return ( 21 |
22 |
23 |
24 | ); 25 | } 26 | }); 27 | 28 | export default Progress; 29 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | var webpackCfg = require('./webpack.config'); 2 | 3 | // Set node environment to testing 4 | process.env.NODE_ENV = 'test'; 5 | 6 | module.exports = function(config) { 7 | config.set({ 8 | basePath: '', 9 | browsers: [ 'PhantomJS' ], 10 | files: [ 11 | 'test/loadtests.js' 12 | ], 13 | port: 8000, 14 | captureTimeout: 60000, 15 | frameworks: [ 'mocha', 'chai' ], 16 | client: { 17 | mocha: {} 18 | }, 19 | singleRun: true, 20 | reporters: [ 'mocha', 'coverage' ], 21 | preprocessors: { 22 | 'test/loadtests.js': [ 'webpack', 'sourcemap' ] 23 | }, 24 | webpack: webpackCfg, 25 | webpackServer: { 26 | noInfo: true 27 | }, 28 | coverageReporter: { 29 | dir: 'coverage/', 30 | reporters: [ 31 | { type: 'html' }, 32 | { type: 'text' } 33 | ] 34 | } 35 | }); 36 | }; 37 | -------------------------------------------------------------------------------- /src/config/README.md: -------------------------------------------------------------------------------- 1 | # About this folder 2 | 3 | This folder holds configuration files for different environments. 4 | You can use it to provide your app with different settings based on the 5 | current environment, e.g. to configure different API base urls depending on 6 | whether your setup runs in dev mode or is built for distribution. 7 | You can include the configuration into your code like this: 8 | 9 | **ES2015 Modules** 10 | 11 | ```js 12 | import config from 'config'; 13 | ``` 14 | 15 | **Common JS** 16 | 17 | Due to Babel6 we need to append `.default`. 18 | 19 | ```js 20 | let config = require('config').default; 21 | ``` 22 | 23 | **Example** 24 | 25 | ```javascript 26 | import React from 'react'; 27 | import config from 'config'; 28 | 29 | class MyComponent extends React.Component { 30 | constructor(props, ctx) { 31 | super(props, ctx); 32 | let currentAppEnv = config.appEnv; 33 | } 34 | } 35 | ``` 36 | -------------------------------------------------------------------------------- /src/components/photo/ControllerUnit.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | /** 4 | * 导航控制条组件 5 | */ 6 | class ControllerUnit extends React.Component { 7 | constructor(props) { 8 | super(props); 9 | this.handleClick = this.handleClick.bind(this); 10 | } 11 | handleClick(e) { 12 | 13 | if (this.props.arrange.isCenter) { 14 | this.props.inverse(); 15 | } else { 16 | this.props.center(); 17 | } 18 | 19 | e.stopPropagation(); 20 | e.preventDefault(); 21 | } 22 | render() { 23 | var controllerUnitClassName = 'controller-unit' 24 | if (this.props.arrange.isCenter) { 25 | controllerUnitClassName += ' is-center'; 26 | if (this.props.arrange.isInverse) { 27 | controllerUnitClassName += ' is-inverse'; 28 | } 29 | } 30 | return ( 31 | 32 | ); 33 | } 34 | } 35 | 36 | export default ControllerUnit; 37 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 基于 React 的音乐相册 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const args = require('minimist')(process.argv.slice(2)); 5 | 6 | // List of allowed environments 7 | const allowedEnvs = ['dev', 'dist', 'test']; 8 | 9 | // Set the correct environment 10 | let env; 11 | if (args._.length > 0 && args._.indexOf('start') !== -1) { 12 | env = 'test'; 13 | } else if (args.env) { 14 | env = args.env; 15 | } else { 16 | env = 'dev'; 17 | } 18 | process.env.REACT_WEBPACK_ENV = env; 19 | 20 | /** 21 | * Build the webpack configuration 22 | * @param {String} wantedEnv The wanted environment 23 | * @return {Object} Webpack config 24 | */ 25 | function buildConfig(wantedEnv) { 26 | let isValid = wantedEnv && wantedEnv.length > 0 && allowedEnvs.indexOf(wantedEnv) !== -1; 27 | let validEnv = isValid ? wantedEnv : 'dev'; 28 | let config = require(path.join(__dirname, 'cfg/' + validEnv)); 29 | return config; 30 | } 31 | 32 | module.exports = buildConfig(env); 33 | -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 基于 React 的音乐相册 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /test/helpers/shallowRenderHelper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Function to get the shallow output for a given component 3 | * As we are using phantom.js, we also need to include the fn.proto.bind shim! 4 | * 5 | * @see http://simonsmith.io/unit-testing-react-components-without-a-dom/ 6 | * @author somonsmith 7 | */ 8 | import React from 'react'; 9 | import TestUtils from 'react-addons-test-utils'; 10 | 11 | /** 12 | * Get the shallow rendered component 13 | * 14 | * @param {Object} component The component to return the output for 15 | * @param {Object} props [optional] The components properties 16 | * @param {Mixed} ...children [optional] List of children 17 | * @return {Object} Shallow rendered output 18 | */ 19 | export default function createComponent(component, props = {}, ...children) { 20 | const shallowRenderer = TestUtils.createRenderer(); 21 | shallowRenderer.render(React.createElement(component, props, children.length > 1 ? children : children[0])); 22 | return shallowRenderer.getRenderOutput(); 23 | } 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 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 | -------------------------------------------------------------------------------- /cfg/dev.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | let path = require('path'); 4 | let webpack = require('webpack'); 5 | let baseConfig = require('./base'); 6 | let defaultSettings = require('./defaults'); 7 | 8 | // Add needed plugins here 9 | let BowerWebpackPlugin = require('bower-webpack-plugin'); 10 | 11 | let config = Object.assign({}, baseConfig, { 12 | entry: [ 13 | 'webpack-dev-server/client?http://127.0.0.1:' + defaultSettings.port, 14 | 'webpack/hot/only-dev-server', 15 | './src/index' 16 | ], 17 | cache: true, 18 | devtool: 'eval-source-map', 19 | plugins: [ 20 | new webpack.HotModuleReplacementPlugin(), 21 | new webpack.NoErrorsPlugin(), 22 | new BowerWebpackPlugin({ 23 | searchResolveModulesDirectories: false 24 | }) 25 | ], 26 | module: defaultSettings.getDefaultModules() 27 | }); 28 | 29 | // Add needed loaders to the defaults here 30 | config.module.loaders.push({ 31 | test: /\.(js|jsx)$/, 32 | loader: 'react-hot!babel-loader', 33 | include: [].concat( 34 | config.additionalPaths, 35 | [ path.join(__dirname, '/../src') ] 36 | ) 37 | }); 38 | 39 | module.exports = config; 40 | -------------------------------------------------------------------------------- /cfg/dist.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | let path = require('path'); 4 | let webpack = require('webpack'); 5 | 6 | let baseConfig = require('./base'); 7 | let defaultSettings = require('./defaults'); 8 | 9 | // Add needed plugins here 10 | let BowerWebpackPlugin = require('bower-webpack-plugin'); 11 | 12 | let config = Object.assign({}, baseConfig, { 13 | entry: path.join(__dirname, '../src/index'), 14 | cache: false, 15 | devtool: 'sourcemap', 16 | plugins: [ 17 | new webpack.optimize.DedupePlugin(), 18 | new webpack.DefinePlugin({ 19 | 'process.env.NODE_ENV': '"production"' 20 | }), 21 | new BowerWebpackPlugin({ 22 | searchResolveModulesDirectories: false 23 | }), 24 | new webpack.optimize.UglifyJsPlugin(), 25 | new webpack.optimize.OccurenceOrderPlugin(), 26 | new webpack.optimize.AggressiveMergingPlugin(), 27 | new webpack.NoErrorsPlugin() 28 | ], 29 | module: defaultSettings.getDefaultModules() 30 | }); 31 | 32 | // Add needed loaders to the defaults here 33 | config.module.loaders.push({ 34 | test: /\.(js|jsx)$/, 35 | loader: 'babel', 36 | include: [].concat( 37 | config.additionalPaths, 38 | [ path.join(__dirname, '/../src') ] 39 | ) 40 | }); 41 | 42 | module.exports = config; 43 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | /*eslint no-console:0 */ 2 | 'use strict'; 3 | require('core-js/fn/object/assign'); 4 | const webpack = require('webpack'); 5 | const WebpackDevServer = require('webpack-dev-server'); 6 | const config = require('./webpack.config'); 7 | const open = require('open'); 8 | 9 | /** 10 | * Flag indicating whether webpack compiled for the first time. 11 | * @type {boolean} 12 | */ 13 | let isInitialCompilation = true; 14 | 15 | const compiler = webpack(config); 16 | 17 | new WebpackDevServer(compiler, config.devServer) 18 | .listen(config.port, 'localhost', (err) => { 19 | if (err) { 20 | console.log(err); 21 | } 22 | console.log('Listening at localhost:' + config.port); 23 | }); 24 | 25 | compiler.plugin('done', () => { 26 | if (isInitialCompilation) { 27 | // Ensures that we log after webpack printed its stats (is there a better way?) 28 | setTimeout(() => { 29 | console.log('\n✓ The bundle is now ready for serving!\n'); 30 | console.log(' Open in iframe mode:\t\x1b[33m%s\x1b[0m', 'http://localhost:' + config.port + '/webpack-dev-server/'); 31 | console.log(' Open in inline mode:\t\x1b[33m%s\x1b[0m', 'http://localhost:' + config.port + '/\n'); 32 | console.log(' \x1b[33mHMR is active\x1b[0m. The bundle will automatically rebuild and live-update on changes.') 33 | }, 350); 34 | } 35 | isInitialCompilation = false; 36 | }); 37 | -------------------------------------------------------------------------------- /cfg/base.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | let path = require('path'); 3 | let defaultSettings = require('./defaults'); 4 | 5 | // Additional npm or bower modules to include in builds 6 | // Add all foreign plugins you may need into this array 7 | // @example: 8 | // let npmBase = path.join(__dirname, '../node_modules'); 9 | // let additionalPaths = [ path.join(npmBase, 'react-bootstrap') ]; 10 | let additionalPaths = []; 11 | 12 | module.exports = { 13 | additionalPaths: additionalPaths, 14 | port: defaultSettings.port, 15 | debug: true, 16 | devtool: 'eval', 17 | output: { 18 | path: path.join(__dirname, '/../dist/assets'), 19 | filename: 'app.js', 20 | publicPath: defaultSettings.publicPath 21 | }, 22 | devServer: { 23 | contentBase: './src/', 24 | historyApiFallback: true, 25 | hot: true, 26 | port: defaultSettings.port, 27 | publicPath: defaultSettings.publicPath, 28 | noInfo: false 29 | }, 30 | resolve: { 31 | extensions: ['', '.js', '.jsx'], 32 | alias: { 33 | actions: `${defaultSettings.srcPath}/actions/`, 34 | components: `${defaultSettings.srcPath}/components/`, 35 | sources: `${defaultSettings.srcPath}/sources/`, 36 | stores: `${defaultSettings.srcPath}/stores/`, 37 | styles: `${defaultSettings.srcPath}/styles/`, 38 | config: `${defaultSettings.srcPath}/config/` + process.env.REACT_WEBPACK_ENV, 39 | 'react/lib/ReactMount': 'react-dom/lib/ReactMount' 40 | } 41 | }, 42 | module: {} 43 | }; 44 | -------------------------------------------------------------------------------- /cfg/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | let path = require('path'); 4 | let srcPath = path.join(__dirname, '/../src/'); 5 | 6 | let baseConfig = require('./base'); 7 | 8 | // Add needed plugins here 9 | let BowerWebpackPlugin = require('bower-webpack-plugin'); 10 | 11 | module.exports = { 12 | devtool: 'eval', 13 | module: { 14 | preLoaders: [ 15 | { 16 | test: /\.(js|jsx)$/, 17 | loader: 'isparta-instrumenter-loader', 18 | include: [ 19 | path.join(__dirname, '/../src') 20 | ] 21 | } 22 | ], 23 | loaders: [ 24 | { 25 | test: /\.(png|jpg|gif|woff|woff2|css|sass|scss|less|styl)$/, 26 | loader: 'null-loader' 27 | }, 28 | { 29 | test: /\.(js|jsx)$/, 30 | loader: 'babel-loader', 31 | include: [].concat( 32 | baseConfig.additionalPaths, 33 | [ 34 | path.join(__dirname, '/../src'), 35 | path.join(__dirname, '/../test') 36 | ] 37 | ) 38 | } 39 | ] 40 | }, 41 | resolve: { 42 | extensions: [ '', '.js', '.jsx' ], 43 | alias: { 44 | actions: srcPath + 'actions/', 45 | helpers: path.join(__dirname, '/../test/helpers'), 46 | components: srcPath + 'components/', 47 | sources: srcPath + 'sources/', 48 | stores: srcPath + 'stores/', 49 | styles: srcPath + 'styles/', 50 | config: srcPath + 'config/' + process.env.REACT_WEBPACK_ENV 51 | } 52 | }, 53 | plugins: [ 54 | new BowerWebpackPlugin({ 55 | searchResolveModulesDirectories: false 56 | }) 57 | ] 58 | }; 59 | -------------------------------------------------------------------------------- /cfg/defaults.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Function that returns default values. 3 | * Used because Object.assign does a shallow instead of a deep copy. 4 | * Using [].push will add to the base array, so a require will alter 5 | * the base array output. 6 | */ 7 | 'use strict'; 8 | 9 | const path = require('path'); 10 | const srcPath = path.join(__dirname, '/../src'); 11 | const dfltPort = 8000; 12 | 13 | /** 14 | * Get the default modules object for webpack 15 | * @return {Object} 16 | */ 17 | function getDefaultModules() { 18 | return { 19 | preLoaders: [ 20 | { 21 | test: /\.(js|jsx)$/, 22 | include: srcPath, 23 | loader: 'eslint-loader' 24 | } 25 | ], 26 | loaders: [ 27 | { 28 | test: /\.css$/, 29 | loader: 'style-loader!css-loader!autoprefixer-loader?{browsers:["last 2 version", "firefox 15"]}!' 30 | }, 31 | { 32 | test: /\.scss/, 33 | loader: 'style-loader!css-loader!autoprefixer-loader?{browsers:["last 2 version", "firefox 15"]}!sass-loader?outputStyle=expanded' 34 | }, 35 | { 36 | test: /\.less/, 37 | loader: 'style-loader!css-loader!less-loader' 38 | }, 39 | { 40 | test: /\.styl/, 41 | loader: 'style-loader!css-loader!stylus-loader' 42 | }, 43 | { 44 | test: /\.(png|jpg|gif|woff|woff2|eot|ttf|svg)$/, 45 | loader: 'url-loader?limit=8192' 46 | }, 47 | { 48 | test: /\.json$/, 49 | loader: 'json-loader' 50 | }, 51 | { 52 | test: /\.(mp4|ogg|svg)$/, 53 | loader: 'file-loader' 54 | } 55 | ] 56 | }; 57 | } 58 | 59 | module.exports = { 60 | srcPath: srcPath, 61 | publicPath: '/assets/', 62 | port: dfltPort, 63 | getDefaultModules: getDefaultModules 64 | }; 65 | -------------------------------------------------------------------------------- /src/components/music/player.less: -------------------------------------------------------------------------------- 1 | .player-page { 2 | width: 550px; 3 | height: 210px; 4 | //margin: auto; 5 | //margin-top: 0px; 6 | 7 | position: absolute; 8 | left: 50%; 9 | transform: translate(-50%, 0); 10 | bottom: 20px; 11 | z-index: 101; 12 | //width: 100%; 13 | 14 | //.caption { 15 | // font-size: 16px; 16 | // color: rgb(47, 152, 66); 17 | //} 18 | 19 | .cover { 20 | width: 180px; 21 | height: 180px; 22 | margin-left: 20px; 23 | 24 | img { 25 | width: 180px; 26 | height: 180px; 27 | border-radius: 50%; 28 | animation: roate 20s infinite linear; // 旋转专辑封面 29 | border:2px solid #808080b8; 30 | } 31 | } 32 | 33 | .volume-container { 34 | position: relative; 35 | left: 20px; 36 | top: -3px; 37 | } 38 | 39 | .volume-container .volume-wrapper { 40 | opacity: 0; 41 | transition: opacity .5s linear; 42 | } 43 | 44 | .volume-container:hover .volume-wrapper { 45 | opacity: 1; 46 | } 47 | 48 | .music-title { 49 | font-size: 25px; 50 | font-weight: 400; 51 | color: rgb(3, 3, 3); 52 | height: 6px; 53 | line-height: 6px; 54 | } 55 | 56 | .music-artist { 57 | font-size: 15px; 58 | font-weight: 400; 59 | color: rgb(74, 74, 74); 60 | } 61 | 62 | .left-time { 63 | font-size: 14px; 64 | color: #999; 65 | font-weight: 400; 66 | width: 40px; 67 | } 68 | 69 | .icon { 70 | cursor: pointer; 71 | } 72 | 73 | .ml20 { 74 | margin-left: 20px; 75 | } 76 | 77 | .mt35 { 78 | margin-top: 35px; 79 | } 80 | 81 | .volume-wrapper { 82 | width: 60px; 83 | display: inline-block; 84 | } 85 | } 86 | 87 | @keyframes roate { 88 | 0% { 89 | transform: rotateZ(0) 90 | } 91 | 100% { 92 | transform: rotateZ(360deg) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/fonts/icons/iconfont.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Created by iconfont 9 | 10 | 11 | 12 | 13 | 21 | 22 | 23 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /dist/assets/47057c894e3bb6696a34ddfc5e9a9387.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Created by iconfont 9 | 10 | 11 | 12 | 13 | 21 | 22 | 23 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/data/musicDatas.js: -------------------------------------------------------------------------------- 1 | export const MUSIC_LIST = [ 2 | { 3 | id: 1, 4 | title: '童话镇', 5 | artist: '陈一发儿', 6 | file: 'https://raw.githubusercontent.com/nnngu/SharedResource/master/music/%E7%AB%A5%E8%AF%9D%E9%95%87.mp3', 7 | cover: 'https://raw.githubusercontent.com/nnngu/FigureBed/master/2018/2/6/tong_hua_zhen.jpg' 8 | }, { 9 | id: 2, 10 | title: '天使中的魔鬼', 11 | artist: '田馥甄', 12 | file: 'http://oj4t8z2d5.bkt.clouddn.com/%E9%AD%94%E9%AC%BC%E4%B8%AD%E7%9A%84%E5%A4%A9%E4%BD%BF.mp3', 13 | cover: 'http://oj4t8z2d5.bkt.clouddn.com/%E9%AD%94%E9%AC%BC%E4%B8%AD%E7%9A%84%E5%A4%A9%E4%BD%BF.jpg' 14 | }, { 15 | id: 3, 16 | title: '风继续吹', 17 | artist: '张国荣', 18 | file: 'http://oj4t8z2d5.bkt.clouddn.com/%E9%A3%8E%E7%BB%A7%E7%BB%AD%E5%90%B9.mp3', 19 | cover: 'http://oj4t8z2d5.bkt.clouddn.com/%E9%A3%8E%E7%BB%A7%E7%BB%AD%E5%90%B9.jpg' 20 | }, { 21 | id: 4, 22 | title: '恋恋风尘', 23 | artist: '老狼', 24 | file: 'http://oj4t8z2d5.bkt.clouddn.com/%E6%81%8B%E6%81%8B%E9%A3%8E%E5%B0%98.mp3', 25 | cover: 'http://oj4t8z2d5.bkt.clouddn.com/%E6%81%8B%E6%81%8B%E9%A3%8E%E5%B0%98.jpg' 26 | }, { 27 | id: 5, 28 | title: '我要你', 29 | artist: '任素汐', 30 | file: 'http://oj4t8z2d5.bkt.clouddn.com/%E6%88%91%E8%A6%81%E4%BD%A0.mp3', 31 | cover: 'http://oj4t8z2d5.bkt.clouddn.com/%E6%88%91%E8%A6%81%E4%BD%A0.jpg' 32 | }, { 33 | id: 6, 34 | title: '成都', 35 | artist: '赵雷', 36 | file: 'http://oj4t8z2d5.bkt.clouddn.com/%E6%88%90%E9%83%BD.mp3', 37 | cover: 'http://oj4t8z2d5.bkt.clouddn.com/%E6%88%90%E9%83%BD.jpg' 38 | }, { 39 | id: 7, 40 | title: 'sound of silence', 41 | artist: 'Simon & Garfunkel', 42 | file: 'http://oj4t8z2d5.bkt.clouddn.com/sound-of-silence.mp3', 43 | cover: 'http://oj4t8z2d5.bkt.clouddn.com/sound-of-silence.jpg' 44 | } 45 | 46 | ]; 47 | -------------------------------------------------------------------------------- /src/components/photo/ImgFigure.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | /** 4 | * 相框组件 5 | */ 6 | class ImgFigure extends React.Component { 7 | 8 | constructor(props) { 9 | super(props); 10 | this.handleClick = this.handleClick.bind(this); 11 | } 12 | 13 | handleClick(e) { 14 | 15 | if (this.props.arrange.isCenter) { 16 | this.props.inverse(); 17 | } else { 18 | this.props.center(); 19 | } 20 | 21 | 22 | e.stopPropagation(); 23 | e.preventDefault(); 24 | } 25 | 26 | render() { 27 | var styleObj = {}; 28 | 29 | //if props assigns the position of pic, use it 30 | if (this.props.arrange.pos) { 31 | styleObj = this.props.arrange.pos; 32 | } 33 | //if props assigns the rotation degree of pic, use it 34 | if (this.props.arrange.rotate) { 35 | (['MozTransform', 'msTransform', 'WebkitTransform', 'transform']).forEach(function(value) { 36 | styleObj[value] = 'rotate(' + this.props.arrange.rotate + 'deg)'; 37 | }.bind(this)); 38 | } 39 | 40 | if (this.props.arrange.isCenter) { 41 | styleObj.zIndex = 11; 42 | } 43 | 44 | var imgFigureClassName = 'img-figure'; 45 | imgFigureClassName += this.props.arrange.isInverse ? ' is-inverse' : ''; 46 | 47 | return ( 48 |
49 | {this.props.data.title} 52 |
53 |

{this.props.data.title}

54 |
55 |

56 | {this.props.data.desc} 57 |

58 |
59 |
60 |
61 | ); 62 | } 63 | } 64 | 65 | export default ImgFigure; 66 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "version": "0.0.1", 4 | "description": "YOUR DESCRIPTION - Generated by generator-react-webpack", 5 | "main": "", 6 | "scripts": { 7 | "start": "node server.js --env=dev", 8 | "test": "karma start", 9 | "test:watch": "karma start --autoWatch=true --singleRun=false", 10 | "posttest": "npm run lint", 11 | "serve": "node server.js --env=dev", 12 | "serve:dist": "node server.js --env=dist", 13 | "dist": "npm run copy & webpack --env=dist", 14 | "lint": "eslint ./src", 15 | "copy": "copyfiles -f ./src/index.html ./src/favicon.ico ./dist", 16 | "clean": "rimraf dist/*", 17 | "release:major": "npm version major && npm publish && git push --follow-tags", 18 | "release:minor": "npm version minor && npm publish && git push --follow-tags", 19 | "release:patch": "npm version patch && npm publish && git push --follow-tags" 20 | }, 21 | "repository": "", 22 | "keywords": [], 23 | "author": "nnngu", 24 | "license": "MIT", 25 | "repository": { 26 | "type": "git", 27 | "url": "https://github.com/nnngu/MusicPhoto" 28 | }, 29 | "devDependencies": { 30 | "autoprefixer-loader": "^3.2.0", 31 | "babel-core": "^6.0.0", 32 | "babel-eslint": "^6.0.0", 33 | "babel-loader": "^6.0.0", 34 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 35 | "babel-polyfill": "^6.3.14", 36 | "babel-preset-es2015": "^6.0.15", 37 | "babel-preset-react": "^6.0.15", 38 | "babel-preset-stage-0": "^6.5.0", 39 | "bower-webpack-plugin": "^0.1.9", 40 | "chai": "^3.2.0", 41 | "copyfiles": "^1.0.0", 42 | "css-loader": "^0.23.0", 43 | "eslint": "^3.0.0", 44 | "eslint-loader": "^1.0.0", 45 | "eslint-plugin-react": "^6.0.0", 46 | "file-loader": "^0.9.0", 47 | "glob": "^7.0.0", 48 | "isparta-instrumenter-loader": "^1.0.0", 49 | "json-loader": "^0.5.7", 50 | "karma": "^1.0.0", 51 | "karma-chai": "^0.1.0", 52 | "karma-coverage": "^1.0.0", 53 | "karma-mocha": "^1.0.0", 54 | "karma-mocha-reporter": "^2.0.0", 55 | "karma-phantomjs-launcher": "^1.0.0", 56 | "karma-sourcemap-loader": "^0.3.5", 57 | "karma-webpack": "^1.7.0", 58 | "minimist": "^1.2.0", 59 | "mocha": "^3.0.0", 60 | "node-sass": "^4.7.2", 61 | "null-loader": "^0.1.1", 62 | "open": "0.0.5", 63 | "phantomjs-prebuilt": "^2.0.0", 64 | "react-addons-test-utils": "^15.0.0", 65 | "react-hot-loader": "^1.2.9", 66 | "rimraf": "^2.4.3", 67 | "sass-loader": "^6.0.6", 68 | "style-loader": "^0.13.0", 69 | "url-loader": "^0.5.6", 70 | "webpack": "^1.15.0", 71 | "webpack-dev-server": "^1.12.0" 72 | }, 73 | "dependencies": { 74 | "core-js": "^2.0.0", 75 | "less": "^2.7.3", 76 | "less-loader": "^4.0.5", 77 | "normalize.css": "^4.0.0", 78 | "pubsub-js": "^1.6.0", 79 | "react": "^15.0.0", 80 | "react-dom": "^15.0.0", 81 | "react-router": "^4.2.0", 82 | "webfonts-loader": "^4.0.1" 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/data/imageDatas.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "fileName": "1.jpg", 4 | "title": "Kuriyama Mirai", 5 | "desc": "We must try our best to bite the bullet and do a thing, Otherwise, we will achieve nothing. ( Hitler ) \n—— 我们必须咬紧牙关,全力以赴去做一件事情;否则,我们将一事无成。 ( 希特勒 )" 6 | }, 7 | { 8 | "fileName": "2.jpg", 9 | "title": "Kuriyama Mirai", 10 | "desc": "Courage is going from failure to failure without losing enthusiasm. ( Churchill ) \n—— 勇气就是不断失败,而不丧失热情。 ( 丘吉尔 )" 11 | }, 12 | { 13 | "fileName": "3.jpg", 14 | "title": "Kuriyama Mirai", 15 | "desc": "We shall draw from the heart of suffering itself the means of inspiration and survival. \n—— 我们应从遭受苦难的心中获得呼吸、生存的方法。" 16 | }, 17 | { 18 | "fileName": "4.jpg", 19 | "title": "Kuriyama Mirai", 20 | "desc": "A pessimist sees the difficulty in every opportunity; an optimist sees the opportunity in every difficulty. \n—— 悲观主义者从每个机遇中看到困难,乐观主义者从每个困难中看到机遇。" 21 | }, 22 | { 23 | "fileName": "5.jpg", 24 | "title": "Kuriyama Mirai", 25 | "desc": "Solitary trees, if they grow at all, grow strong. \n—— 孤独的树如果成长,就会成长得非常茁壮。" 26 | }, 27 | { 28 | "fileName": "6.jpg", 29 | "title": "Kuriyama Mirai", 30 | "desc": "History is written by the victors. \n—— 历史是由胜利者书写的。" 31 | }, 32 | { 33 | "fileName": "7.jpg", 34 | "title": "Kuriyama Mirai", 35 | "desc": "Never leave that until tomorrow , which you can do today. \n—— 今天的事不要拖到明天。" 36 | }, 37 | { 38 | "fileName": "8.jpg", 39 | "title": "Kuriyama Mirai", 40 | "desc": "Most folks are about as happy as they make up their minds to be. \n—— 对于大多数人来说,他们认定自己有多幸福,就有多幸福。" 41 | }, 42 | { 43 | "fileName": "9.jpg", 44 | "title": "Kuriyama Mirai", 45 | "desc": "Where there is a will , there is a way. \n—— 有志者,事竟成。" 46 | }, 47 | { 48 | "fileName": "10.jpg", 49 | "title": "Kuriyama Mirai", 50 | "desc": "Nothing is as important as passion. No matter what you want to do with your life, be passionate. \n—— 没有什么比热情更重要了,无论你想做什么,带着热情去做。" 51 | }, 52 | { 53 | "fileName": "11.jpg", 54 | "title": "Kuriyama Mirai", 55 | "desc": "I do not know anyone who has got to the top without hard work. That is the recipe. It will not always get you to the top, but should get you pretty near. \n—— 我不知道有人能不努力就取得成功卓越,这就是方法,努力不会让你每次都获得成功卓越,但能让你更靠近它许多。" 56 | }, 57 | { 58 | "fileName": "12.jpg", 59 | "title": "Kuriyama Mirai", 60 | "desc": "Man are disturbed not by things, but by the view which they take of them. \n—— 人们的困扰不是来自事情的本身,而是来自他们对事情的看法。" 61 | }, 62 | { 63 | "fileName": "13.jpg", 64 | "title": "Kuriyama Mirai", 65 | "desc": "Defeat is not bitter unless you swallow it. \n—— 挫败并不痛苦,除非你接受它。" 66 | }, 67 | { 68 | "fileName": "14.jpg", 69 | "title": "Kuriyama Mirai", 70 | "desc": "Life’s challenges are not supposed to paralyze you — they’re supposed to help you discover who you are. \n—— 人生的挑战不是要瘫痪你,而是帮你发现真实的你。" 71 | }, 72 | { 73 | "fileName": "15.jpg", 74 | "title": "Kuriyama Mirai", 75 | "desc": "Many of life’s failures are experienced by people who did not realize how close they were to success when they gave up. \n—— 很多人失败,因为他们不知道他们放弃时,距离成功是多么的近。" 76 | }, 77 | { 78 | "fileName": "16.jpg", 79 | "title": "Kuriyama Mirai", 80 | "desc": "There is no disgrace in honest failure; there is disgrace in fearing to fail. \n—— 坦率的失败并不丢脸,害怕失败才会丢脸。" 81 | } 82 | ] 83 | -------------------------------------------------------------------------------- /src/styles/App.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'iconfont'; 3 | src: url('../fonts/icons/iconfont.eot'); 4 | src: url('../fonts/icons/iconfont.eot?#iefix') format('embedded-opentype'), 5 | url('../fonts/icons/iconfont.woff') format('woff'), 6 | url('../fonts/icons/iconfont.ttf') format('truetype'), 7 | url('../fonts/icons/iconfont.svg#iconfont') format('svg'); 8 | } 9 | 10 | html, body { 11 | width: 100%; 12 | height: 100%; 13 | background-color: #222; 14 | } 15 | 16 | .content { 17 | width: 100%; 18 | height: 100%; 19 | } 20 | 21 | /* stage -- start */ 22 | .stage { 23 | position: relative; 24 | width: 100%; 25 | height: 100%; 26 | } 27 | /* stage -- end */ 28 | 29 | /* image -- start */ 30 | .img-sec { 31 | position: relative; 32 | overflow: hidden; 33 | width: 100%; 34 | height: 100%; 35 | //height: 680px; 36 | perspective: 1800px; 37 | background-color: #ddd; 38 | 39 | @at-root { 40 | .img-figure { 41 | position: absolute; 42 | width: 320px; 43 | height: 360px; 44 | margin: 0; 45 | padding: 30px; 46 | background-color: #FFF; 47 | box-sizing: border-box; 48 | cursor: pointer; 49 | transform-origin: 0 50% 0; 50 | transform-style: preserve-3d; 51 | transition: transform .6s ease-in-out, left .6s ease-in-out, top .6s ease-in-out; 52 | -webkit-transition: -webkit-transform .6s ease-in-out, left .6s ease-in-out, top .6s ease-in-out; 53 | &.is-inverse { 54 | transform: translate(320px) rotateY(180deg); 55 | } 56 | } 57 | 58 | .img-back { 59 | position: absolute; 60 | top: 0; 61 | left: 0; 62 | 63 | width: 100%; 64 | height: 100%; 65 | padding: 30px; 66 | overflow: auto; 67 | 68 | color: #666; 69 | // font-size: 22px; 70 | text-align: center; 71 | // line-height: 1.25; 72 | 73 | background-color: #fff; 74 | 75 | box-sizing: border-box; 76 | transform: rotateY(180deg) translateZ(1px); 77 | backface-visibility: hidden; 78 | -moz-backface-visibility: hidden; 79 | 80 | // p { 81 | // margin: 0; 82 | // } 83 | } 84 | 85 | figcaption { 86 | text-align: center; 87 | 88 | .img-title { 89 | margin: 15px 0 0 0; 90 | color: #a7a0a2; 91 | font-size: 15px; 92 | } 93 | } 94 | } 95 | } 96 | /* image -- end */ 97 | 98 | /* controller -- start */ 99 | .controller-nav { 100 | position: absolute; 101 | left: 0; 102 | bottom: 250px; 103 | z-index: 101; 104 | width: 100%; 105 | text-align: center; 106 | 107 | @at-root { 108 | .controller-unit { 109 | display: inline-block; 110 | margin: 0 5px; 111 | width: 30px; 112 | height: 30px; 113 | 114 | text-align: center; 115 | vertical-align: middle; 116 | cursor: pointer; 117 | background-color: #aaa; 118 | border-radius: 50%; 119 | 120 | transform: scale(.5); 121 | transition: transform .6s ease-in-out, background-color .3s; 122 | -webkit-transition: transform .6s ease, background-color .3s; 123 | 124 | &.is-center { 125 | background-color: #888; 126 | 127 | transform: scale(1); 128 | 129 | &::after { 130 | font-family: "iconfont"; 131 | color: #fff; 132 | font-size: 80%; 133 | line-height: 30px; 134 | content: "\e600"; 135 | 136 | -webkit-font-smoothing: antialiased; 137 | -moz-font-smoothing: greyscale; 138 | } 139 | 140 | &.is-inverse { 141 | background-color: #555; 142 | transform: rotateY(180deg) 143 | } 144 | } 145 | } 146 | } 147 | } 148 | /* controller -- end */ 149 | 150 | /* source area -- start */ 151 | .source-area { 152 | position: absolute; 153 | left: 0; 154 | bottom: 5px; 155 | z-index: 101; 156 | width: 100%; 157 | text-align: center; 158 | } 159 | /* source area -- end */ 160 | -------------------------------------------------------------------------------- /src/styles/common.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | .container { 4 | } 5 | .styleguide,.styleguide * 6 | { 7 | box-sizing: border-box; 8 | -webkit-box-sizing: border-box; 9 | } 10 | 11 | .clearfix:after,.clearfix:before 12 | { 13 | content: " "; 14 | display: table; 15 | } 16 | .clearfix:after 17 | { 18 | clear: both; 19 | } 20 | .rt { 21 | position: relative; 22 | } 23 | .ab { 24 | position: absolute; 25 | } 26 | .tar { 27 | text-align: right; 28 | } 29 | .ft12 { 30 | font-size: 12px; 31 | } 32 | .bold { 33 | font-weight: bold; 34 | } 35 | .-vcenter:before 36 | { 37 | content: ""; 38 | display: inline-block; 39 | height: 100%; 40 | margin-right: -4px; 41 | vertical-align: middle; 42 | } 43 | .text-overflow { 44 | text-overflow: ellipsis; 45 | overflow: hidden; 46 | white-space: nowrap; 47 | } 48 | .-hcenter 49 | { 50 | margin-left: auto; 51 | margin-right: auto; 52 | text-align: center; 53 | } 54 | .none { 55 | display: none; 56 | } 57 | .mt10 { 58 | margin-top: 10px; 59 | } 60 | .mt20 { 61 | margin-top: 20px; 62 | } 63 | .row 64 | { 65 | display: -webkit-box!important; 66 | display: -webkit-flex!important; 67 | -webkit-align-items: center; 68 | -webkit-box-align: center; 69 | width: 100%; 70 | } 71 | .-align-top.btn-group,.-align-top.flex-row,.row.-align-top 72 | { 73 | -webkit-align-items: flex-start; 74 | -webkit-box-align: start; 75 | } 76 | .row>* 77 | { 78 | display: block!important; 79 | -webkit-box-flex: 1; 80 | -webkit-flex: 1; 81 | } 82 | .row>.-col-auto 83 | { 84 | display: block!important; 85 | -webkit-box-flex: 0; 86 | -webkit-flex: 0 0 auto; 87 | } 88 | .-col1 89 | { 90 | -webkit-box-flex: 0!important; 91 | -webkit-flex: none!important; 92 | width: 8.33333%!important; 93 | } 94 | .-col2 95 | { 96 | -webkit-box-flex: 0!important; 97 | -webkit-flex: none!important; 98 | width: 16.66667%!important; 99 | } 100 | .-col3 101 | { 102 | -webkit-box-flex: 0!important; 103 | -webkit-flex: none!important; 104 | width: 25%!important; 105 | } 106 | .-col4 107 | { 108 | -webkit-box-flex: 0!important; 109 | -webkit-flex: none!important; 110 | width: 33.33333%!important; 111 | } 112 | .-col5 113 | { 114 | -webkit-box-flex: 0!important; 115 | -webkit-flex: none!important; 116 | width: 41.66667%!important; 117 | } 118 | .-col6 119 | { 120 | -webkit-box-flex: 0!important; 121 | -webkit-flex: none!important; 122 | width: 50%!important; 123 | } 124 | .-col7 125 | { 126 | -webkit-box-flex: 0!important; 127 | -webkit-flex: none!important; 128 | width: 58.33333%!important; 129 | } 130 | .-col8 131 | { 132 | -webkit-box-flex: 0!important; 133 | -webkit-flex: none!important; 134 | width: 66.66667%!important; 135 | } 136 | .-col9 137 | { 138 | -webkit-box-flex: 0!important; 139 | -webkit-flex: none!important; 140 | width: 75%!important; 141 | } 142 | .-col10 143 | { 144 | -webkit-box-flex: 0!important; 145 | -webkit-flex: none!important; 146 | width: 83.33333%!important; 147 | } 148 | .-col11 149 | { 150 | -webkit-box-flex: 0!important; 151 | -webkit-flex: none!important; 152 | width: 91.66667%!important; 153 | } 154 | .-col12 155 | { 156 | -webkit-box-flex: 0!important; 157 | -webkit-flex: none!important; 158 | width: 100%!important; 159 | } 160 | 161 | .fl{ float: left;} 162 | .fr{ float: right;} 163 | 164 | .icon { 165 | display: inline-block; 166 | width: 32px; 167 | height: 32px; 168 | background: url(../icons.png) no-repeat; 169 | background-size: 32px auto; 170 | } 171 | .icon.prev { 172 | background-position: 0 0; 173 | } 174 | .icon.next { 175 | background-position: 0 -32px; 176 | } 177 | .icon.pause { 178 | background-position: 0 -64px; 179 | } 180 | .icon.play { 181 | background-position: 0 -96px; 182 | } 183 | .icon.repeat-once { 184 | background-position: 0 -128px; 185 | } 186 | .icon.repeat-cycle { 187 | background-position: 0 -160px; 188 | } 189 | .icon.repeat-random { 190 | background-position: 0 -192px; 191 | } 192 | .icon-volume { 193 | display: inline-block; 194 | width: 16px; 195 | height: 16px; 196 | background: url(../icons.png) no-repeat; 197 | background-size: 16px auto; 198 | background-position: 0 -112px; 199 | } 200 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 基于 React + Webpack 的音乐相册 2 | 3 | 笔记仓库:[https://github.com/nnngu/LearningNotes](https://github.com/nnngu/LearningNotes) 4 | 5 | 演示地址:[https://nnngu.github.io/MusicPhoto/](https://nnngu.github.io/MusicPhoto/) 6 | 7 | --- 8 | 9 | ## 快速部署 10 | 11 | * `npm install` 12 | * `npm run start` 13 | 14 | --- 15 | 16 | [上一篇文章用爬虫自动下载了一些图片](https://github.com/nnngu/LearningNotes/blob/master/Spider/02%20Python%E7%88%AC%E8%99%AB%E5%AE%9E%E7%8E%B0%E7%99%BE%E5%BA%A6%E5%9B%BE%E7%89%87%E8%87%AA%E5%8A%A8%E4%B8%8B%E8%BD%BD.md),这一篇就用这些图片做一个音乐相册吧! 17 | 18 | ## 效果预览 19 | 20 | ![][1] 21 | 22 | 点击图片,切换到背面: 23 | 24 | ![][2] 25 | 26 | ### 演示地址 27 | 28 | 演示地址:[https://nnngu.github.io/MusicPhoto/](https://nnngu.github.io/MusicPhoto/) 29 | 30 | ## 环境搭建 31 | 32 | 1、安装 npm,安装成功后,在终端输入 `npm -v` 可以查看它的版本。 33 | 34 | ![][3] 35 | 36 | 2、安装`generator-react-webpack`,使用如下命令: 37 | 38 | ``` 39 | npm install -g generator-react-webpack 40 | ``` 41 | 42 | 安装完成之后,输入`npm list --depth=0 -global` 可以查看版本。 43 | 44 | ![][4] 45 | 46 | 3、创建项目,打开你用来存放代码的目录,然后输入:`yo react-webpack MusicPhoto` 47 | 48 | 4、创建完成,项目的目录如下图: 49 | 50 | ![][5] 51 | 52 | 需要注意的几个地方: 53 | 54 | * ① cfg 目录是配置文件所在的目录 55 | * 重点关注 cfg 目录里面的 defaults.js 文件 56 | * ② src 项目的源代码主要在这里面 57 | * ③ package.json 用来管理和配置依赖模块 58 | 59 | ## 添加 autoprefixer-loader 模块 60 | 61 | autoprefixer-loader 是用来处理 css 的模块,安装命令: 62 | 63 | ``` 64 | npm install autoprefixer-loader --save-dev 65 | ``` 66 | 67 | 然后打开 cfg 目录中的 defaults.js 添加如下配置信息: 68 | 69 | ![][6] 70 | 71 | ## 添加 json-loader 模块 72 | 73 | json-loader 是用来处理 json 的模块,安装命令: 74 | 75 | ``` 76 | npm install json-loader --save-dev 77 | ``` 78 | 79 | 然后打开 cfg 目录中的 defaults.js 添加如下配置信息: 80 | 81 | ![][7] 82 | 83 | ## 添加图片 84 | 85 | 1、在 src 目录下添加 images 目录和一些图片,如下图:(图片尺寸全部是 260 \* 260) 86 | 87 | ![][8] 88 | 89 | 2、添加 imageDatas.json 如下图: 90 | 91 | ![][9] 92 | 93 | imageDatas.json 里面的代码请参照项目的源代码。 94 | 95 | 3、在`src/components/Main.js`中引入`imageDatas.json` 代码如下: 96 | 97 | ```javascript 98 | // 获取图片的 json 数据 99 | var imagesData = require('../data/imageDatas.json'); 100 | ``` 101 | 102 | 4、根据图片的文件名,生成图片URL。 103 | 104 | *src/components/Main.js* 105 | 106 | ```javascript 107 | /** 108 | * @imagesDataArray {Array} 109 | * @return {Array} 110 | */ 111 | imagesData = (function getImageURL(imagesDataArray) { 112 | for (var i = 0, j = imagesDataArray.length; i < j; i++) { 113 | var singleImageData = imagesDataArray[i]; 114 | 115 | singleImageData.imageURL = require('../images/' + singleImageData.fileName); 116 | 117 | imagesDataArray[i] = singleImageData; 118 | } 119 | return imagesDataArray; 120 | })(imagesData); 121 | ``` 122 | 123 | ## 配置字体 124 | 125 | 打开 cfg 目录中的 defaults.js 添加如下配置信息: 126 | 127 | ![][10] 128 | 129 | ## 组件的绑定 130 | 131 | src/index.html 中的关键代码: 132 | 133 | ![][11] 134 | 135 | src/index.js 中的关键代码: 136 | 137 | ![][12] 138 | 139 | ## 代码逻辑 140 | 141 | 主要的代码逻辑在 `Main.js`中,主要的布局样式在 `App.scss`中。如下图: 142 | 143 | ![][13] 144 | 145 | 具体代码请参照项目的源代码 [https://github.com/nnngu/MusicPhoto](https://github.com/nnngu/MusicPhoto) 146 | 147 | ## 发布到Github Pages 148 | 149 | 1、修改`cfg/defaults.js`中的`publicPath`,改为`publicPath: './assets/',` 如下图: 150 | 151 | ![][14] 152 | 153 | 2、打包,使用`npm run dist`指令。打包完成可以看到如下目录: 154 | 155 | ![][15] 156 | 157 | 3、把打包好的目录 push 到 GitHub 的 gh-pages 分支,使用如下命令: 158 | 159 | ``` 160 | git subtree push --prefix=dist origin gh-pages 161 | ``` 162 | 163 | 4、在GitHub 对应的仓库里面开启 Github Pages 功能,并选择 `gh-pages`分支即可。 164 | 165 | **👇👇👇下一篇将会总结完成音乐播放器的过程。👇👇👇** 166 | 167 | [05 (项目) 基于 React + Webpack 的音乐相册(下)](https://github.com/nnngu/LearningNotes/blob/master/React/05%20(%E9%A1%B9%E7%9B%AE)%20%E5%9F%BA%E4%BA%8E%20React%20%2B%20Webpack%20%E7%9A%84%E9%9F%B3%E4%B9%90%E7%9B%B8%E5%86%8C%EF%BC%88%E4%B8%8B%EF%BC%89.md) 168 | 169 | 170 | [1]: https://www.github.com/nnngu/FigureBed/raw/master/2018/2/5/1517842690437.jpg 171 | [2]: https://www.github.com/nnngu/FigureBed/raw/master/2018/2/5/1517842775081.jpg 172 | [3]: https://www.github.com/nnngu/FigureBed/raw/master/2018/2/6/1517848578071.jpg 173 | [4]: https://www.github.com/nnngu/FigureBed/raw/master/2018/2/6/1517848855856.jpg 174 | [5]: https://www.github.com/nnngu/FigureBed/raw/master/2018/2/6/1517849337904.jpg 175 | [6]: https://www.github.com/nnngu/FigureBed/raw/master/2018/2/6/1517850101903.jpg 176 | [7]: https://www.github.com/nnngu/FigureBed/raw/master/2018/2/6/1517850270658.jpg 177 | [8]: https://www.github.com/nnngu/FigureBed/raw/master/2018/2/6/1517851778975.jpg 178 | [9]: https://www.github.com/nnngu/FigureBed/raw/master/2018/2/6/1517851939423.jpg 179 | [10]: https://www.github.com/nnngu/FigureBed/raw/master/2018/2/6/1517852817008.jpg 180 | [11]: https://www.github.com/nnngu/FigureBed/raw/master/2018/2/6/1517853041622.jpg 181 | [12]: https://www.github.com/nnngu/FigureBed/raw/master/2018/2/6/1517853081657.jpg 182 | [13]: https://www.github.com/nnngu/FigureBed/raw/master/2018/2/6/1517853295536.jpg 183 | [14]: https://www.github.com/nnngu/FigureBed/raw/master/2018/2/6/1517853662271.jpg 184 | [15]: https://www.github.com/nnngu/FigureBed/raw/master/2018/2/6/1517853876038.jpg 185 | -------------------------------------------------------------------------------- /src/components/music/player.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Progress from './progress'; 3 | import {MUSIC_LIST} from '../../data/musicDatas'; 4 | 5 | let PubSub = require('pubsub-js'); 6 | require('./player.less'); 7 | 8 | let duration = null; 9 | 10 | let Player = React.createClass({ 11 | 12 | /** 13 | * 生命周期方法 componentDidMount 14 | */ 15 | componentDidMount() { 16 | $('#player').bind($.jPlayer.event.timeupdate, (e) => { 17 | duration = e.jPlayer.status.duration; 18 | this.setState({ 19 | progress: e.jPlayer.status.currentPercentAbsolute, 20 | volume: e.jPlayer.options.volume * 100, 21 | leftTime: this.formatTime(duration * (1 - e.jPlayer.status.currentPercentAbsolute / 100)) 22 | }); 23 | }); 24 | 25 | $('#player').bind($.jPlayer.event.ended, (e) => { 26 | this.playNext(); 27 | }); 28 | }, 29 | 30 | /** 31 | * 生命周期方法 componentWillUnmount 32 | */ 33 | componentWillUnmount() { 34 | $('#player').unbind($.jPlayer.event.timeupdate); 35 | }, 36 | 37 | formatTime(time) { 38 | time = Math.floor(time); 39 | let miniute = Math.floor(time / 60); 40 | let seconds = Math.floor(time % 60); 41 | 42 | return miniute + ':' + (seconds < 10 ? '0' + seconds : seconds); 43 | }, 44 | 45 | /** 46 | * 进度条被拖动的处理方法 47 | * @param progress 48 | */ 49 | changeProgressHandler(progress) { 50 | $('#player').jPlayer('play', duration * progress); 51 | this.setState({ 52 | isPlay: true 53 | }); 54 | 55 | // 获取转圈的封面图片 56 | let imgAnimation = this.refs.imgAnimation; 57 | imgAnimation.style = 'animation-play-state: running'; 58 | }, 59 | 60 | /** 61 | * 音量条被拖动的处理方法 62 | * @param progress 63 | */ 64 | changeVolumeHandler(progress) { 65 | $('#player').jPlayer('volume', progress); 66 | }, 67 | 68 | /** 69 | * 播放或者暂停方法 70 | **/ 71 | play() { 72 | this.setState({ 73 | isPlay: !this.state.isPlay 74 | }); 75 | 76 | // 获取转圈的封面图片 77 | var imgAnimation = this.refs.imgAnimation; 78 | 79 | if (this.state.isPlay) { 80 | $('#player').jPlayer('pause'); 81 | imgAnimation.style = 'animation-play-state: paused'; 82 | } else { 83 | $('#player').jPlayer('play'); 84 | imgAnimation.style = 'animation-play-state: running'; 85 | } 86 | 87 | }, 88 | 89 | /** 90 | * 下一首 91 | **/ 92 | next() { 93 | PubSub.publish('PLAY_NEXT'); 94 | 95 | this.setState({ 96 | isPlay: true 97 | }); 98 | 99 | // 开始播放下一首 100 | this.playNext(); 101 | 102 | // 获取转圈的封面图片 103 | let imgAnimation = this.refs.imgAnimation; 104 | imgAnimation.style = 'animation-play-state: running'; 105 | 106 | }, 107 | 108 | /** 109 | * 上一首 110 | **/ 111 | prev() { 112 | PubSub.publish('PLAY_PREV'); 113 | 114 | this.setState({ 115 | isPlay: true 116 | }); 117 | 118 | // 开始播放上一首 119 | this.playNext('prev'); 120 | 121 | // 获取转圈的封面图片 122 | let imgAnimation = this.refs.imgAnimation; 123 | imgAnimation.style = 'animation-play-state: running'; 124 | 125 | }, 126 | 127 | playNext(type = 'next') { 128 | let index = this.findMusicIndex(this.state.currentMusitItem); 129 | if (type === 'next') { 130 | index = (index + 1) % this.state.musicList.length; 131 | } else { 132 | index = (index + this.state.musicList.length - 1) % this.state.musicList.length; 133 | } 134 | let musicItem = this.state.musicList[index]; 135 | this.setState({ 136 | currentMusitItem: musicItem 137 | }); 138 | this.playMusic(musicItem); 139 | }, 140 | 141 | playMusic(item) { 142 | $('#player').jPlayer('setMedia', { 143 | mp3: item.file 144 | }).jPlayer('play'); 145 | this.setState({ 146 | currentMusitItem: item 147 | }); 148 | }, 149 | 150 | findMusicIndex(music) { 151 | let index = this.state.musicList.indexOf(music); 152 | return Math.max(0, index); 153 | }, 154 | 155 | changeRepeat() { 156 | PubSub.publish('CHANAGE_REPEAT'); 157 | }, 158 | 159 | // constructor() { 160 | // return { 161 | // progress: 0, 162 | // volume: 0, 163 | // isPlay: true, 164 | // leftTime: '' 165 | // } 166 | // }, 167 | 168 | componentWillMount() { 169 | this.getInitialState(); 170 | }, 171 | 172 | getInitialState() { 173 | return { 174 | musicList: MUSIC_LIST, 175 | currentMusitItem: MUSIC_LIST[0], 176 | repeatType: 'cycle', 177 | 178 | progress: 0, 179 | volume: 0, 180 | isPlay: true, 181 | leftTime: '' 182 | } 183 | }, 184 | 185 | /** 186 | * render 渲染方法 187 | * @returns {*} 188 | */ 189 | render() { 190 | return ( 191 |
192 |
193 |
194 |

{this.state.currentMusitItem.title}

195 |

{this.state.currentMusitItem.artist}

196 |
197 |
-{this.state.leftTime}
198 |
199 | 200 |
201 | 202 | {/* 音量条 */} 203 | 208 | 209 |
210 |
211 |
212 |
213 | 214 | {/* 播放进度条 */} 215 | 219 | 220 |
221 |
222 |
223 | 224 | 225 | 226 |
227 |
228 | {/* 播放模式按钮:单曲、循环、随机 */} 229 | 230 |
231 |
232 |
233 |
234 | {this.state.currentMusitItem.title}/ 235 |
236 |
237 |
238 | ); 239 | } 240 | }); 241 | 242 | export default Player; 243 | -------------------------------------------------------------------------------- /src/components/Main.js: -------------------------------------------------------------------------------- 1 | require('normalize.css/normalize.css'); 2 | require('styles/App.scss'); 3 | 4 | import React from 'react'; 5 | import ReactDOM from 'react-dom'; 6 | 7 | // 导入相册相关的组件 8 | import ImgFigure from './photo/ImgFigure'; 9 | import ControllerUnit from './photo/ControllerUnit'; 10 | 11 | // 获取图片的 json 数据 12 | var imagesData = require('../data/imageDatas.json'); 13 | 14 | // 导入音乐相关的组件 15 | import Player from './music/player'; 16 | 17 | // 歌名、歌手、播放URL等数据 18 | import {MUSIC_LIST} from '../data/musicDatas'; 19 | 20 | import {randomRange} from './utils/util'; 21 | 22 | require('../styles/common.css'); 23 | 24 | let PubSub = require('pubsub-js'); 25 | 26 | /** 27 | * @imagesDataArray {Array} 28 | * @return {Array} 29 | */ 30 | imagesData = (function getImageURL(imagesDataArray) { 31 | for (var i = 0, j = imagesDataArray.length; i < j; i++) { 32 | var singleImageData = imagesDataArray[i]; 33 | 34 | singleImageData.imageURL = require('../images/' + singleImageData.fileName); 35 | 36 | imagesDataArray[i] = singleImageData; 37 | } 38 | return imagesDataArray; 39 | })(imagesData); 40 | 41 | /** 42 | * 获取指定范围内的随机值 43 | * @param {min} 44 | * @param {max} 45 | * @return {random between min and max} 46 | */ 47 | function getRangeRandom(low, high) { 48 | return Math.floor(Math.random() * (high - low) + low); 49 | } 50 | 51 | /** 52 | * 获取 0到30 之间的随机旋转度数 53 | * @return {random degree between 0 and 30} 54 | */ 55 | function get30DegRandom() { 56 | return ((Math.random() > 0.5 ? '' : '-') + Math.floor(Math.random() * 30)); 57 | } 58 | 59 | /** 60 | * 总组件 61 | */ 62 | class AppComponent extends React.Component { 63 | /** 64 | * 构造方法 65 | * @param props 66 | */ 67 | constructor(props) { 68 | super(props); 69 | this.Constant = { 70 | centerPos: { // 中心位置 71 | left: 0, 72 | top: 0 73 | }, 74 | hPosRange: { // 水平方向 75 | leftSecX: [0, 0], 76 | rightSecX: [0, 0], 77 | y: [0, 0] 78 | }, 79 | vPosRange: { // 垂直方向 80 | x: [0, 0], 81 | topY: [0, 0] 82 | } 83 | }; 84 | this.state = { 85 | imgArrangeArr: [] 86 | }; 87 | 88 | 89 | // 这里不能用 return 90 | // return { 91 | // musicList: MUSIC_LIST, 92 | // currentMusitItem: {}, 93 | // repeatType: 'cycle', 94 | // 95 | // progress: 0, 96 | // volume: 0, 97 | // isPlay: true, 98 | // leftTime: '' 99 | // } 100 | 101 | } 102 | 103 | /** 104 | * 反转图片,参数为图片的索引 105 | * @param {index of pic to be rotated} 106 | * @return {a closure function, return a function} 107 | */ 108 | inverse(index) { 109 | return function () { 110 | var imgArrangeArr = this.state.imgArrangeArr; 111 | 112 | imgArrangeArr[index].isInverse = !imgArrangeArr[index].isInverse; 113 | 114 | this.setState({ 115 | imgArrangeArr: imgArrangeArr 116 | }); 117 | }.bind(this); 118 | } 119 | 120 | /** 121 | * 重新排列所有的图片 122 | * @param {the index of pic to be centered} 123 | * @return {[type]} 124 | */ 125 | rearrange(centerIndex) { 126 | let imgArrangeArr = this.state.imgArrangeArr, 127 | Constant = this.Constant, 128 | centerPos = Constant.centerPos, 129 | hPosRange = Constant.hPosRange, 130 | vPosRange = Constant.vPosRange, 131 | hPosRangeLeftSecX = hPosRange.leftSecX, 132 | hPosRangeRigheSecX = hPosRange.rightSecX, 133 | hPosRangeY = hPosRange.y, 134 | vPosRangeTopY = vPosRange.topY, 135 | vPosRangeX = vPosRange.x, 136 | 137 | imgArrangeTopArr = [], 138 | topImgNum = Math.floor(Math.random() * 2), // there would be 0 or 1 pics in top sec 139 | topImgSpliceIndex = 0, // the index of pic at top sec 140 | 141 | imgArrangeCenterArr = imgArrangeArr.splice(centerIndex, 1); // get the center pic 142 | 143 | // get the info of pics in up sec 144 | topImgSpliceIndex = Math.floor(Math.random() * (imgArrangeArr.length - topImgNum)); 145 | imgArrangeTopArr = imgArrangeArr.splice(topImgSpliceIndex, topImgNum); 146 | 147 | /*---- position part ----*/ 148 | // 让图片居中 149 | imgArrangeCenterArr[0] = { 150 | pos: centerPos, 151 | rotate: 0, 152 | isCenter: true 153 | } 154 | 155 | // 让图片在最上层 156 | imgArrangeTopArr.forEach(function (value, index) { 157 | imgArrangeTopArr[index] = { 158 | pos: { 159 | top: getRangeRandom(vPosRangeTopY[0], vPosRangeTopY[1]), 160 | left: getRangeRandom(vPosRangeX[0], vPosRangeX[1]) 161 | }, 162 | rotate: get30DegRandom(), 163 | isCenter: false 164 | }; 165 | }); 166 | 167 | // let pics in the left and right sec positioned 168 | // now imgArrangeArr only has pics to be position in left and right sec 169 | for (var i = 0, j = imgArrangeArr.length, k = j / 2; i < j; i++) { 170 | let hPosRangeLORX = null; 171 | 172 | // first half pics at left 173 | // rest pics at right 174 | if (i < k) { 175 | hPosRangeLORX = hPosRangeLeftSecX; 176 | } else { 177 | hPosRangeLORX = hPosRangeRigheSecX; 178 | } 179 | 180 | imgArrangeArr[i] = { 181 | pos: { 182 | left: getRangeRandom(hPosRangeLORX[0], hPosRangeLORX[1]), 183 | top: getRangeRandom(hPosRangeY[0], hPosRangeY[1]) 184 | }, 185 | rotate: get30DegRandom(), 186 | isCenter: false 187 | }; 188 | } 189 | 190 | if (imgArrangeTopArr && imgArrangeTopArr[0]) { 191 | imgArrangeArr.splice(topImgSpliceIndex, 0, imgArrangeTopArr[0]); 192 | } 193 | 194 | imgArrangeArr.splice(centerIndex, 0, imgArrangeCenterArr[0]); 195 | 196 | this.setState({ 197 | imgArrangeArr: imgArrangeArr 198 | }) 199 | } 200 | 201 | /** 202 | * use rearrange() to center pic 203 | * @param {index of pic to be centered} 204 | * @return {function} 205 | */ 206 | center(index) { 207 | return function () { 208 | this.rearrange(index); 209 | }.bind(this); 210 | } 211 | 212 | /** 213 | * 生命周期:componentDidMount 214 | * 计算它们的位置范围 215 | */ 216 | componentDidMount() { 217 | // get the size of stage 218 | let stageDOM = ReactDOM.findDOMNode(this.refs.stage), 219 | stageW = stageDOM.scrollWidth, 220 | stageH = stageDOM.scrollHeight, 221 | halfStageW = Math.ceil(stageW / 2), 222 | halfStageH = Math.ceil(stageH / 2); 223 | // get the size of one music 224 | let imgFigureDOM = ReactDOM.findDOMNode(this.refs.imgFigure0), 225 | imgW = imgFigureDOM.scrollWidth, 226 | imgH = imgFigureDOM.scrollHeight, 227 | halfImgW = Math.ceil(imgW / 2), 228 | halfImgH = Math.ceil(imgH / 2); 229 | // 计算中心图片的位置 230 | this.Constant.centerPos = { 231 | left: halfStageW - halfImgW, 232 | top: halfStageH - halfImgH - 140 233 | } 234 | // calculate the range of posiztion for the left and right sec 235 | this.Constant.hPosRange.leftSecX[0] = -halfImgW; 236 | this.Constant.hPosRange.leftSecX[1] = halfStageW - halfImgW * 3; 237 | 238 | this.Constant.hPosRange.rightSecX[0] = halfStageW + halfImgW; 239 | this.Constant.hPosRange.rightSecX[1] = stageW - halfImgW; 240 | 241 | this.Constant.hPosRange.y[0] = -halfImgH; 242 | this.Constant.hPosRange.y[1] = stageH - halfImgH * 3; 243 | // calculate the range of position for up sec 244 | this.Constant.vPosRange.x[0] = halfStageW - imgW; 245 | this.Constant.vPosRange.x[1] = halfStageW; 246 | 247 | this.Constant.vPosRange.topY[0] = 0 - halfImgH; 248 | this.Constant.vPosRange.topY[1] = halfStageH - halfImgH * 3; 249 | // let the first pic at center 250 | this.rearrange(0); 251 | 252 | /** 播放音乐相关的 start */ 253 | 254 | $('#player').jPlayer({ 255 | supplied: 'mp3', 256 | wmode: 'window', 257 | useStateClassSkin: true 258 | }); 259 | 260 | this.playMusic(MUSIC_LIST[0]); 261 | 262 | $('#player').bind($.jPlayer.event.ended, (e) => { 263 | this.playWhenEnd(); 264 | }); 265 | PubSub.subscribe('PLAY_MUSIC', (msg, item) => { 266 | this.playMusic(item); 267 | }); 268 | PubSub.subscribe('DEL_MUSIC', (msg, item) => { 269 | this.setState({ 270 | musicList: this.state.musicList.filter((music) => { 271 | return music !== item; 272 | }) 273 | }); 274 | }); 275 | PubSub.subscribe('PLAY_NEXT', () => { 276 | this.playNext(); 277 | }); 278 | PubSub.subscribe('PLAY_PREV', () => { 279 | this.playNext('prev'); 280 | }); 281 | let repeatList = [ 282 | 'cycle', 283 | 'once', 284 | 'random' 285 | ]; 286 | PubSub.subscribe('CHANAGE_REPEAT', () => { 287 | let index = repeatList.indexOf(this.state.repeatType); 288 | index = (index + 1) % repeatList.length; 289 | this.setState({ 290 | repeatType: repeatList[index] 291 | }); 292 | }); 293 | 294 | /** 播放音乐相关的 end */ 295 | 296 | } 297 | 298 | componentWillUnmount() { 299 | PubSub.unsubscribe('PLAY_MUSIC'); 300 | PubSub.unsubscribe('DEL_MUSIC'); 301 | PubSub.unsubscribe('CHANAGE_REPEAT'); 302 | PubSub.unsubscribe('PLAY_NEXT'); 303 | PubSub.unsubscribe('PLAY_PREV'); 304 | } 305 | 306 | componentWillMount() { 307 | this.getInitialState(); 308 | } 309 | 310 | /** 音乐相关的方法 start */ 311 | 312 | getInitialState() { 313 | return { 314 | musicList: MUSIC_LIST, 315 | currentMusitItem: MUSIC_LIST[0], 316 | repeatType: 'cycle' 317 | 318 | // progress: 0, 319 | // volume: 0, 320 | // isPlay: true, 321 | // leftTime: '' 322 | } 323 | } 324 | 325 | playWhenEnd() { 326 | if (this.state.repeatType === 'random') { 327 | let index = this.findMusicIndex(this.state.currentMusitItem); 328 | let randomIndex = randomRange(0, this.state.musicList.length - 1); 329 | while (randomIndex === index) { 330 | randomIndex = randomRange(0, this.state.musicList.length - 1); 331 | } 332 | this.playMusic(this.state.musicList[randomIndex]); 333 | } else if (this.state.repeatType === 'once') { 334 | this.playMusic(this.state.currentMusitItem); 335 | } else { 336 | this.playNext(); 337 | } 338 | } 339 | 340 | playNext(type = 'next') { 341 | let index = this.findMusicIndex(this.state.currentMusitItem); 342 | if (type === 'next') { 343 | index = (index + 1) % this.state.musicList.length; 344 | } else { 345 | index = (index + this.state.musicList.length - 1) % this.state.musicList.length; 346 | } 347 | let musicItem = this.state.musicList[index]; 348 | this.setState({ 349 | currentMusitItem: musicItem 350 | }); 351 | this.playMusic(musicItem); 352 | } 353 | 354 | findMusicIndex(music) { 355 | let index = this.state.musicList.indexOf(music); 356 | return Math.max(0, index); 357 | } 358 | 359 | playMusic(item) { 360 | $('#player').jPlayer('setMedia', { 361 | mp3: item.file 362 | }).jPlayer('play'); 363 | this.setState({ 364 | currentMusitItem: item 365 | }); 366 | } 367 | 368 | /** 音乐相关的方法 end */ 369 | 370 | 371 | /** 372 | * render 方法 373 | * @returns {*} 374 | */ 375 | render() { 376 | /* declare 2 units*/ 377 | var controllerUnits = [], 378 | imgFigures = []; 379 | 380 | Array.prototype.forEach.call(imagesData, function (value, index) { 381 | if (!this.state.imgArrangeArr[index]) { 382 | this.state.imgArrangeArr[index] = { 383 | pos: { 384 | left: 0, 385 | top: 0 386 | }, 387 | rotate: 0, 388 | isInverse: false, 389 | isCenter: false 390 | } 391 | } 392 | imgFigures.push(); 395 | controllerUnits.push(); 397 | }.bind(this)); 398 | 399 | return ( 400 |
401 | {/* 相册相关的组件 */} 402 |
403 | {imgFigures} 404 |
405 | 408 | 409 | {/* 音乐相关的组件 */} 410 |
411 | 412 |
413 | 414 | {/* 源代码组件 */} 415 |
416 | 源代码:https://github.com/nnngu/MusicPhoto 417 |
418 | 419 |
420 | ); 421 | } 422 | } 423 | 424 | AppComponent.defaultProps = {}; 425 | 426 | export default AppComponent; 427 | --------------------------------------------------------------------------------