├── LICENSE ├── README.md ├── config ├── env.js ├── jest │ ├── cssTransform.js │ └── fileTransform.js ├── paths.js ├── webpack.config.js └── webpackDevServer.config.js ├── package.json ├── public ├── favicon.ico ├── index.html └── manifest.json ├── scripts ├── build.js ├── start.js └── test.js └── src ├── App.css ├── App.js ├── App.test.js ├── component ├── PageA.js ├── PageB.js ├── PageC.js └── lib │ └── PageHeader.js ├── css └── lib │ ├── common.css │ ├── pageheader.css │ └── reset.css ├── images ├── WX20190325-003909@2x.png ├── WX20190325-005512@2x.png ├── WX20190325-005603@2x.png ├── home.png └── larrow.png ├── index.css ├── index.js ├── logo.svg └── serviceWorker.js /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 曹小科 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 最近在react 上配置使用vw 适配方案,总体表现优秀。所以很多同学们都在观望vw 适配到底行不行,靠不靠谱。 2 | 3 | 在这里,从我在项目中实战应用下来但经验来说,可行,靠谱! 4 | 5 | 简单配置,一劳永逸,高可靠的还原设计师的稿子,给你底气和设计师对标UI。 6 | 7 | 欢迎挑战还原度,从此做一个自信的前端工程师。哈哈~ 8 | 9 | 一定要看到底,可能有你会遇到坑的解药!拿走不谢++++ 给个小星星吧 10 | 11 | 12 | 该demo 为vw 适配方案 react 16 && react-router-dom 4+ 搭配使用,一个多页面路由项目. 13 | 14 | 步骤: 15 | 16 | 1、创建项目 && 初始化 17 | ```html 18 | create-react-app react-vw-layout 19 | 20 | cd react-vw-layout 21 | ``` 22 | 23 | 2、开启我们的配置之路&&简单配置 24 | 25 | react 把配置都隐藏了,需要我们手动来显示配置 26 | ``` 27 | npm run eject 28 | 29 | //Are you sure you want to eject? This action is permanent. (y/N) y 30 | ``` 31 | 32 | 之后可以看到 config文件 && scripts 文件夹 33 | 34 | ![Image text](https://github.com/caoxiaoke/react-vw-layout/blob/master/src/images/WX20190325-003909%402x.png) 35 |
36 | 37 | 3、安装我们需要配置用到的插件(建议使用cnpm) 38 | ```html 39 | npm install --save postcss-aspect-ratio-mini postcss-px-to-viewport postcss-write-svg postcss-cssnext postcss-viewport-units cssnano 40 | ``` 41 | 42 | 43 | 4、开始配置 44 | 45 | 在config/webpack.config.js 中修改代码 46 | 47 | 先引入postcss cssnano插件 48 | 49 | ```html 50 | const postcssAspectRatioMini = require('postcss-aspect-ratio-mini'); 51 | const postcssPxToViewport = require('postcss-px-to-viewport'); 52 | const postcssWriteSvg = require('postcss-write-svg'); 53 | const postcssCssnext = require('postcss-cssnext'); 54 | const postcssViewportUnits = require('postcss-viewport-units'); 55 | const cssnano = require('cssnano'); 56 | ``` 57 | 58 | 增加postcss 配置 59 | 60 | { 61 | // Options for PostCSS as we reference these options twice 62 | // Adds vendor prefixing based on your specified browser support in 63 | // package.json 64 | loader: require.resolve('postcss-loader'), 65 | options: { 66 | // Necessary for external CSS imports to work 67 | // https://github.com/facebook/create-react-app/issues/2677 68 | ident: 'postcss', 69 | plugins: () => [ 70 | require('postcss-flexbugs-fixes'), 71 | require('postcss-preset-env')({ 72 | autoprefixer: { 73 | flexbox: 'no-2009', 74 | }, 75 | stage: 3, 76 | }), 77 | //在这个位置加入我们需要配置的代码 78 | //在这个位置加入我们需要配置的代码 79 | //在这个位置加入我们需要配置的代码 80 | postcssAspectRatioMini({}), 81 | postcssPxToViewport({ 82 | viewportWidth: 750, // (Number) The width of the viewport. 83 | viewportHeight: 1334, // (Number) The height of the viewport. 84 | unitPrecision: 3, // (Number) The decimal numbers to allow the REM units to grow to. 85 | viewportUnit: 'vw', // (String) Expected units. 86 | selectorBlackList: ['.ignore', '.hairlines', '.list-row-bottom-line', '.list-row-top-line'], // (Array) The selectors to ignore and leave as px. 87 | minPixelValue: 1, // (Number) Set the minimum pixel value to replace. 88 | mediaQuery: false // (Boolean) Allow px to be converted in media queries. 89 | }), 90 | postcssWriteSvg({ 91 | utf8: false 92 | }), 93 | postcssPresetEnv({}), 94 | // postcssViewportUnits({ 95 | // filterRule: rule => rule.selector.indexOf('::after') === -1 && rule.selector.indexOf('::before') === -1 && rule.selector.indexOf(':after') === -1 && rule.selector.indexOf(':before') === -1 96 | // }), 97 | postcssViewportUnits({}), 98 | cssnano({ 99 | "cssnano-preset-advanced": { 100 | zindex: false, 101 | autoprefixer: false 102 | }, 103 | }) 104 | //在这个位置加入我们需要配置的代码 105 | //在这个位置加入我们需要配置的代码 106 | //在这个位置加入我们需要配置的代码 107 | ], 108 | sourceMap: isEnvProduction && shouldUseSourceMap, 109 | }, 110 | } 111 | 112 | 5、配置完成以后,我们可以看到vw 神奇的效果了 113 | 114 | ```html 115 | npm run start 116 | ``` 117 | 118 | 浏览器会打开 http://localhost:8080/ 119 | ```html 120 | .App { 121 | background-color: bisque; 122 | display: flex; 123 | color: white; 124 | justify-content: center; 125 | align-items: center; 126 | font-size: 30px; 127 | height: 200px; 128 | width: 750px; 129 | } 130 | ``` 131 | 132 | ![Image text](https://github.com/caoxiaoke/react-vw-layout/blob/master/src/images/WX20190325-005512%402x.png) 133 |
134 | 135 | 6、我们加入兼容我们android 低版本的机型hack 加入viewport-units-buggyfill配置 136 | 137 | 同学们,经过实战配置viewport-units-buggyfill之后,4.2 4.3 4.4+ 系统版本上表现都 ok,项目中主要使用flexbox 布局 138 | 139 | demo 版本中直接引入阿里的cdn 文件,在自己项目中可以放自己公司的服务器或者项目中。 140 | 141 | 打开public/index.html,在head 中插入 viewport-units-buggyfill 和body 中使用 142 | 143 | 144 | ```html 145 | 146 | 147 | 148 | 149 | 150 | 157 | 158 | ``` 159 | 160 | 最后完整的index.html 161 | 162 | ```html 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 176 | 177 | 186 | react-vw-layout 187 | 188 | 189 | 190 | 191 | 192 |
193 | 203 | 210 | 211 | 212 | ``` 213 | 214 | 215 | 项目中有遇到的坑: 216 | ```html 217 | 1、cssnano 如果你的版本为:4+ 以上,请在配置中如下方案配置: 218 | 219 | cssnano({ 220 | "cssnano-preset-advanced": { 221 | zindex: false, 222 | autoprefixer: false 223 | }, 224 | }) 225 | 226 | 我遇到了始终把你定位的z-index值重新计算为:1,巨坑,不然你会一口老血喷出来的。 227 | 228 | 和cssnano 3+版本配置不一样。 229 | 230 | 231 | 2、ios 系统下img不显示问题,解决方案如下: 232 | 233 | /*兼容ios不显示图片问题*/ 234 | img { 235 | content: normal !important 236 | } 237 | 238 | 239 | 3、1px 问题,解决方案 240 | 241 | /*1px 解决方案*/ 242 | 243 | @svg square { 244 | @rect { 245 | fill: var(--color, white); 246 | width: 100%; 247 | height: 50%; 248 | } 249 | } 250 | 251 | .example-line { 252 | width: 100%; 253 | background: white svg(square param(--color #E6E6E6)); 254 | background-size: 100% 1px; 255 | background-repeat: no-repeat; 256 | background-position: bottom left; 257 | } 258 | 259 | /*1px 解决方案*/ 260 | 261 | /*伪元素1px*/ 262 | 263 | .row-cell:before { 264 | content: " "; 265 | position: absolute; 266 | left: 0; 267 | top: 0; 268 | right: 0; 269 | height: 1px; 270 | border-top: 1px solid #e5e5e5; 271 | color: #e5e5e5; 272 | transform-origin: 0 0; 273 | transform: scaleY(0.5); 274 | z-index: 2; 275 | } 276 | ``` 277 | 278 | 如果你不使用react 也不使用vue ,在项目中只使用html页面 vw实现移动端适配,请点这 《如何在html项目中使用vw实现移动端适配》 279 | 280 | ![Image text](https://github.com/caoxiaoke/react-vw-layout/blob/master/src/images/home.png) 281 |
282 | 283 | 284 | ![Image text](https://github.com/caoxiaoke/react-vw-layout/blob/master/src/images/WX20190325-005603%402x.png) 285 |
286 | -------------------------------------------------------------------------------- /config/env.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const paths = require('./paths'); 6 | 7 | // Make sure that including paths.js after env.js will read .env variables. 8 | delete require.cache[require.resolve('./paths')]; 9 | 10 | const NODE_ENV = process.env.NODE_ENV; 11 | if (!NODE_ENV) { 12 | throw new Error( 13 | 'The NODE_ENV environment variable is required but was not specified.' 14 | ); 15 | } 16 | 17 | // https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use 18 | var dotenvFiles = [ 19 | `${paths.dotenv}.${NODE_ENV}.local`, 20 | `${paths.dotenv}.${NODE_ENV}`, 21 | // Don't include `.env.local` for `test` environment 22 | // since normally you expect tests to produce the same 23 | // results for everyone 24 | NODE_ENV !== 'test' && `${paths.dotenv}.local`, 25 | paths.dotenv, 26 | ].filter(Boolean); 27 | 28 | // Load environment variables from .env* files. Suppress warnings using silent 29 | // if this file is missing. dotenv will never modify any environment variables 30 | // that have already been set. Variable expansion is supported in .env files. 31 | // https://github.com/motdotla/dotenv 32 | // https://github.com/motdotla/dotenv-expand 33 | dotenvFiles.forEach(dotenvFile => { 34 | if (fs.existsSync(dotenvFile)) { 35 | require('dotenv-expand')( 36 | require('dotenv').config({ 37 | path: dotenvFile, 38 | }) 39 | ); 40 | } 41 | }); 42 | 43 | // We support resolving modules according to `NODE_PATH`. 44 | // This lets you use absolute paths in imports inside large monorepos: 45 | // https://github.com/facebook/create-react-app/issues/253. 46 | // It works similar to `NODE_PATH` in Node itself: 47 | // https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders 48 | // Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. 49 | // Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims. 50 | // https://github.com/facebook/create-react-app/issues/1023#issuecomment-265344421 51 | // We also resolve them to make sure all tools using them work consistently. 52 | const appDirectory = fs.realpathSync(process.cwd()); 53 | process.env.NODE_PATH = (process.env.NODE_PATH || '') 54 | .split(path.delimiter) 55 | .filter(folder => folder && !path.isAbsolute(folder)) 56 | .map(folder => path.resolve(appDirectory, folder)) 57 | .join(path.delimiter); 58 | 59 | // Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be 60 | // injected into the application via DefinePlugin in Webpack configuration. 61 | const REACT_APP = /^REACT_APP_/i; 62 | 63 | function getClientEnvironment(publicUrl) { 64 | const raw = Object.keys(process.env) 65 | .filter(key => REACT_APP.test(key)) 66 | .reduce( 67 | (env, key) => { 68 | env[key] = process.env[key]; 69 | return env; 70 | }, 71 | { 72 | // Useful for determining whether we’re running in production mode. 73 | // Most importantly, it switches React into the correct mode. 74 | NODE_ENV: process.env.NODE_ENV || 'development', 75 | // Useful for resolving the correct path to static assets in `public`. 76 | // For example, . 77 | // This should only be used as an escape hatch. Normally you would put 78 | // images into the `src` and `import` them in code to get their paths. 79 | PUBLIC_URL: publicUrl, 80 | } 81 | ); 82 | // Stringify all values so we can feed into Webpack DefinePlugin 83 | const stringified = { 84 | 'process.env': Object.keys(raw).reduce((env, key) => { 85 | env[key] = JSON.stringify(raw[key]); 86 | return env; 87 | }, {}), 88 | }; 89 | 90 | return { raw, stringified }; 91 | } 92 | 93 | module.exports = getClientEnvironment; 94 | -------------------------------------------------------------------------------- /config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/en/webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | // This is a custom Jest transformer turning file imports into filenames. 6 | // http://facebook.github.io/jest/docs/en/webpack.html 7 | 8 | module.exports = { 9 | process(src, filename) { 10 | const assetFilename = JSON.stringify(path.basename(filename)); 11 | 12 | if (filename.match(/\.svg$/)) { 13 | return `const React = require('react'); 14 | module.exports = { 15 | __esModule: true, 16 | default: ${assetFilename}, 17 | ReactComponent: React.forwardRef((props, ref) => ({ 18 | $$typeof: Symbol.for('react.element'), 19 | type: 'svg', 20 | ref: ref, 21 | key: null, 22 | props: Object.assign({}, props, { 23 | children: ${assetFilename} 24 | }) 25 | })), 26 | };`; 27 | } 28 | 29 | return `module.exports = ${assetFilename};`; 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /config/paths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const url = require('url'); 6 | const pkg = require('../package.json'); 7 | // Make sure any symlinks in the project folder are resolved: 8 | // https://github.com/facebook/create-react-app/issues/637 9 | const appDirectory = fs.realpathSync(process.cwd()); 10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath); 11 | 12 | const envPublicUrl = process.env.PUBLIC_URL; 13 | 14 | function ensureSlash(inputPath, needsSlash) { 15 | const hasSlash = inputPath.endsWith('/'); 16 | if (hasSlash && !needsSlash) { 17 | return inputPath.substr(0, inputPath.length - 1); 18 | } else if (!hasSlash && needsSlash) { 19 | return `${inputPath}/`; 20 | } else { 21 | return inputPath; 22 | } 23 | } 24 | 25 | const getPublicUrl = appPackageJson => 26 | envPublicUrl || require(appPackageJson).homepage; 27 | 28 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer 29 | // "public path" at which the app is served. 30 | // Webpack needs to know it to put the right 26 | 27 | 28 | 29 |
30 | 40 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /scripts/build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'production'; 5 | process.env.NODE_ENV = 'production'; 6 | 7 | // Makes the script crash on unhandled rejections instead of silently 8 | // ignoring them. In the future, promise rejections that are not handled will 9 | // terminate the Node.js process with a non-zero exit code. 10 | process.on('unhandledRejection', err => { 11 | throw err; 12 | }); 13 | 14 | // Ensure environment variables are read. 15 | require('../config/env'); 16 | 17 | 18 | const path = require('path'); 19 | const chalk = require('react-dev-utils/chalk'); 20 | const fs = require('fs-extra'); 21 | const webpack = require('webpack'); 22 | const bfj = require('bfj'); 23 | const configFactory = require('../config/webpack.config'); 24 | const paths = require('../config/paths'); 25 | const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); 26 | const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages'); 27 | const printHostingInstructions = require('react-dev-utils/printHostingInstructions'); 28 | const FileSizeReporter = require('react-dev-utils/FileSizeReporter'); 29 | const printBuildError = require('react-dev-utils/printBuildError'); 30 | 31 | const measureFileSizesBeforeBuild = 32 | FileSizeReporter.measureFileSizesBeforeBuild; 33 | const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild; 34 | const useYarn = fs.existsSync(paths.yarnLockFile); 35 | 36 | // These sizes are pretty large. We'll warn for bundles exceeding them. 37 | const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024; 38 | const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024; 39 | 40 | const isInteractive = process.stdout.isTTY; 41 | 42 | // Warn and crash if required files are missing 43 | if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { 44 | process.exit(1); 45 | } 46 | 47 | // Process CLI arguments 48 | const argv = process.argv.slice(2); 49 | const writeStatsJson = argv.indexOf('--stats') !== -1; 50 | 51 | // Generate configuration 52 | const config = configFactory('production'); 53 | 54 | // We require that you explicitly set browsers and do not fall back to 55 | // browserslist defaults. 56 | const { checkBrowsers } = require('react-dev-utils/browsersHelper'); 57 | checkBrowsers(paths.appPath, isInteractive) 58 | .then(() => { 59 | // First, read the current file sizes in build directory. 60 | // This lets us display how much they changed later. 61 | return measureFileSizesBeforeBuild(paths.appBuild); 62 | }) 63 | .then(previousFileSizes => { 64 | // Remove all content but keep the directory so that 65 | // if you're in it, you don't end up in Trash 66 | fs.emptyDirSync(paths.appBuild); 67 | // Merge with the public folder 68 | copyPublicFolder(); 69 | // Start the webpack build 70 | return build(previousFileSizes); 71 | }) 72 | .then( 73 | ({ stats, previousFileSizes, warnings }) => { 74 | if (warnings.length) { 75 | console.log(chalk.yellow('Compiled with warnings.\n')); 76 | console.log(warnings.join('\n\n')); 77 | console.log( 78 | '\nSearch for the ' + 79 | chalk.underline(chalk.yellow('keywords')) + 80 | ' to learn more about each warning.' 81 | ); 82 | console.log( 83 | 'To ignore, add ' + 84 | chalk.cyan('// eslint-disable-next-line') + 85 | ' to the line before.\n' 86 | ); 87 | } else { 88 | console.log(chalk.green('Compiled successfully.\n')); 89 | } 90 | 91 | console.log('File sizes after gzip:\n'); 92 | printFileSizesAfterBuild( 93 | stats, 94 | previousFileSizes, 95 | paths.appBuild, 96 | WARN_AFTER_BUNDLE_GZIP_SIZE, 97 | WARN_AFTER_CHUNK_GZIP_SIZE 98 | ); 99 | console.log(); 100 | 101 | const appPackage = require(paths.appPackageJson); 102 | const publicUrl = paths.publicUrl; 103 | const publicPath = config.output.publicPath; 104 | const buildFolder = path.relative(process.cwd(), paths.appBuild); 105 | printHostingInstructions( 106 | appPackage, 107 | publicUrl, 108 | publicPath, 109 | buildFolder, 110 | useYarn 111 | ); 112 | }, 113 | err => { 114 | console.log(chalk.red('Failed to compile.\n')); 115 | printBuildError(err); 116 | process.exit(1); 117 | } 118 | ) 119 | .catch(err => { 120 | if (err && err.message) { 121 | console.log(err.message); 122 | } 123 | process.exit(1); 124 | }); 125 | 126 | // Create the production build and print the deployment instructions. 127 | function build(previousFileSizes) { 128 | console.log('Creating an optimized production build...'); 129 | 130 | let compiler = webpack(config); 131 | return new Promise((resolve, reject) => { 132 | compiler.run((err, stats) => { 133 | let messages; 134 | if (err) { 135 | if (!err.message) { 136 | return reject(err); 137 | } 138 | messages = formatWebpackMessages({ 139 | errors: [err.message], 140 | warnings: [], 141 | }); 142 | } else { 143 | messages = formatWebpackMessages( 144 | stats.toJson({ all: false, warnings: true, errors: true }) 145 | ); 146 | } 147 | if (messages.errors.length) { 148 | // Only keep the first error. Others are often indicative 149 | // of the same problem, but confuse the reader with noise. 150 | if (messages.errors.length > 1) { 151 | messages.errors.length = 1; 152 | } 153 | return reject(new Error(messages.errors.join('\n\n'))); 154 | } 155 | if ( 156 | process.env.CI && 157 | (typeof process.env.CI !== 'string' || 158 | process.env.CI.toLowerCase() !== 'false') && 159 | messages.warnings.length 160 | ) { 161 | console.log( 162 | chalk.yellow( 163 | '\nTreating warnings as errors because process.env.CI = true.\n' + 164 | 'Most CI servers set it automatically.\n' 165 | ) 166 | ); 167 | return reject(new Error(messages.warnings.join('\n\n'))); 168 | } 169 | 170 | const resolveArgs = { 171 | stats, 172 | previousFileSizes, 173 | warnings: messages.warnings, 174 | }; 175 | if (writeStatsJson) { 176 | return bfj 177 | .write(paths.appBuild + '/bundle-stats.json', stats.toJson()) 178 | .then(() => resolve(resolveArgs)) 179 | .catch(error => reject(new Error(error))); 180 | } 181 | 182 | return resolve(resolveArgs); 183 | }); 184 | }); 185 | } 186 | 187 | function copyPublicFolder() { 188 | fs.copySync(paths.appPublic, paths.appBuild, { 189 | dereference: true, 190 | filter: file => file !== paths.appHtml, 191 | }); 192 | } 193 | -------------------------------------------------------------------------------- /scripts/start.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'development'; 5 | process.env.NODE_ENV = 'development'; 6 | 7 | // Makes the script crash on unhandled rejections instead of silently 8 | // ignoring them. In the future, promise rejections that are not handled will 9 | // terminate the Node.js process with a non-zero exit code. 10 | process.on('unhandledRejection', err => { 11 | throw err; 12 | }); 13 | 14 | // Ensure environment variables are read. 15 | require('../config/env'); 16 | 17 | 18 | const fs = require('fs'); 19 | const chalk = require('react-dev-utils/chalk'); 20 | const webpack = require('webpack'); 21 | const WebpackDevServer = require('webpack-dev-server'); 22 | const clearConsole = require('react-dev-utils/clearConsole'); 23 | const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); 24 | const { 25 | choosePort, 26 | createCompiler, 27 | prepareProxy, 28 | prepareUrls, 29 | } = require('react-dev-utils/WebpackDevServerUtils'); 30 | const openBrowser = require('react-dev-utils/openBrowser'); 31 | const paths = require('../config/paths'); 32 | const configFactory = require('../config/webpack.config'); 33 | const createDevServerConfig = require('../config/webpackDevServer.config'); 34 | 35 | const useYarn = fs.existsSync(paths.yarnLockFile); 36 | const isInteractive = process.stdout.isTTY; 37 | 38 | // Warn and crash if required files are missing 39 | if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { 40 | process.exit(1); 41 | } 42 | 43 | // Tools like Cloud9 rely on this. 44 | const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 8080; 45 | const HOST = process.env.HOST || '0.0.0.0'; 46 | 47 | if (process.env.HOST) { 48 | console.log( 49 | chalk.cyan( 50 | `Attempting to bind to HOST environment variable: ${chalk.yellow( 51 | chalk.bold(process.env.HOST) 52 | )}` 53 | ) 54 | ); 55 | console.log( 56 | `If this was unintentional, check that you haven't mistakenly set it in your shell.` 57 | ); 58 | console.log( 59 | `Learn more here: ${chalk.yellow('https://bit.ly/CRA-advanced-config')}` 60 | ); 61 | console.log(); 62 | } 63 | 64 | // We require that you explictly set browsers and do not fall back to 65 | // browserslist defaults. 66 | const { checkBrowsers } = require('react-dev-utils/browsersHelper'); 67 | checkBrowsers(paths.appPath, isInteractive) 68 | .then(() => { 69 | // We attempt to use the default port but if it is busy, we offer the user to 70 | // run on a different port. `choosePort()` Promise resolves to the next free port. 71 | return choosePort(HOST, DEFAULT_PORT); 72 | }) 73 | .then(port => { 74 | if (port == null) { 75 | // We have not found a port. 76 | return; 77 | } 78 | const config = configFactory('development'); 79 | const protocol = process.env.HTTPS === 'true' ? 'https' : 'http'; 80 | const appName = require(paths.appPackageJson).name; 81 | const useTypeScript = fs.existsSync(paths.appTsConfig); 82 | const urls = prepareUrls(protocol, HOST, port); 83 | const devSocket = { 84 | warnings: warnings => 85 | devServer.sockWrite(devServer.sockets, 'warnings', warnings), 86 | errors: errors => 87 | devServer.sockWrite(devServer.sockets, 'errors', errors), 88 | }; 89 | // Create a webpack compiler that is configured with custom messages. 90 | const compiler = createCompiler({ 91 | appName, 92 | config, 93 | devSocket, 94 | urls, 95 | useYarn, 96 | useTypeScript, 97 | webpack, 98 | }); 99 | // Load proxy config 100 | const proxySetting = require(paths.appPackageJson).proxy; 101 | const proxyConfig = prepareProxy(proxySetting, paths.appPublic); 102 | // Serve webpack assets generated by the compiler over a web server. 103 | const serverConfig = createDevServerConfig( 104 | proxyConfig, 105 | urls.lanUrlForConfig 106 | ); 107 | const devServer = new WebpackDevServer(compiler, serverConfig); 108 | // Launch WebpackDevServer. 109 | devServer.listen(port, HOST, err => { 110 | if (err) { 111 | return console.log(err); 112 | } 113 | if (isInteractive) { 114 | clearConsole(); 115 | } 116 | console.log(chalk.cyan('Starting the development server...\n')); 117 | openBrowser(urls.localUrlForBrowser); 118 | }); 119 | 120 | ['SIGINT', 'SIGTERM'].forEach(function(sig) { 121 | process.on(sig, function() { 122 | devServer.close(); 123 | process.exit(); 124 | }); 125 | }); 126 | }) 127 | .catch(err => { 128 | if (err && err.message) { 129 | console.log(err.message); 130 | } 131 | process.exit(1); 132 | }); 133 | -------------------------------------------------------------------------------- /scripts/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'test'; 5 | process.env.NODE_ENV = 'test'; 6 | process.env.PUBLIC_URL = ''; 7 | 8 | // Makes the script crash on unhandled rejections instead of silently 9 | // ignoring them. In the future, promise rejections that are not handled will 10 | // terminate the Node.js process with a non-zero exit code. 11 | process.on('unhandledRejection', err => { 12 | throw err; 13 | }); 14 | 15 | // Ensure environment variables are read. 16 | require('../config/env'); 17 | 18 | 19 | const jest = require('jest'); 20 | const execSync = require('child_process').execSync; 21 | let argv = process.argv.slice(2); 22 | 23 | function isInGitRepository() { 24 | try { 25 | execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' }); 26 | return true; 27 | } catch (e) { 28 | return false; 29 | } 30 | } 31 | 32 | function isInMercurialRepository() { 33 | try { 34 | execSync('hg --cwd . root', { stdio: 'ignore' }); 35 | return true; 36 | } catch (e) { 37 | return false; 38 | } 39 | } 40 | 41 | // Watch unless on CI, in coverage mode, explicitly adding `--no-watch`, 42 | // or explicitly running all tests 43 | if ( 44 | !process.env.CI && 45 | argv.indexOf('--coverage') === -1 && 46 | argv.indexOf('--no-watch') === -1 && 47 | argv.indexOf('--watchAll') === -1 48 | ) { 49 | // https://github.com/facebook/create-react-app/issues/5210 50 | const hasSourceControl = isInGitRepository() || isInMercurialRepository(); 51 | argv.push(hasSourceControl ? '--watch' : '--watchAll'); 52 | } 53 | 54 | // Jest doesn't have this option so we'll remove it 55 | if (argv.indexOf('--no-watch') !== -1) { 56 | argv = argv.filter(arg => arg !== '--no-watch'); 57 | } 58 | 59 | 60 | jest.run(argv); 61 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | background-color: bisque; 3 | display: flex; 4 | color: white; 5 | justify-content: center; 6 | align-items: center; 7 | font-size: 30px; 8 | height: 200px; 9 | width: 750px; 10 | } 11 | 12 | .App-logo { 13 | animation: App-logo-spin infinite 20s linear; 14 | height: 80px; 15 | pointer-events: none; 16 | } 17 | 18 | .App-header { 19 | background-color: #282c34; 20 | min-height: 100vh; 21 | display: flex; 22 | flex-direction: column; 23 | align-items: center; 24 | justify-content: center; 25 | font-size: calc(10px + 2vmin); 26 | color: white; 27 | } 28 | 29 | .App-link { 30 | color: #61dafb; 31 | } 32 | 33 | .list { 34 | margin-top: 150px; 35 | } 36 | 37 | .list-row { 38 | margin: 50px 0; 39 | font-size: 28px; 40 | } 41 | 42 | @keyframes App-logo-spin { 43 | from { 44 | transform: rotate(0deg); 45 | } 46 | to { 47 | transform: rotate(360deg); 48 | } 49 | } 50 | 51 | .test { 52 | background-color: bisque; 53 | display: flex; 54 | color: white; 55 | justify-content: center; 56 | align-items: center; 57 | font-size: 30px; 58 | height: 200px; 59 | width: 750px; 60 | } 61 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {Route, Switch, NavLink, Redirect, withRouter, Link} from 'react-router-dom' 3 | import Loadable from 'react-loadable'; 4 | import PageA from './component/PageA'; 5 | import PageB from './component/PageB'; 6 | import PageC from './component/PageC'; 7 | import PageHeader from './component/lib/PageHeader'; 8 | 9 | import './css/lib/reset.css'; 10 | import './css/lib/common.css'; 11 | import './css/lib/pageheader.css'; 12 | import './App.css'; 13 | 14 | const PageAComponent = Loadable({ 15 | loader: () => import('./component/PageA'), 16 | loading: PageA, 17 | }); 18 | const PageBComponent = Loadable({ 19 | loader: () => import('./component/PageB'), 20 | loading: PageB, 21 | }); 22 | const PageCComponent = Loadable({ 23 | loader: () => import('./component/PageC'), 24 | loading: PageC, 25 | }); 26 | const HomeComponent = () => { 27 | return (
28 | 29 |
30 |
31 | Link-To-PageA 32 |
33 |
34 | Link-To-PageB 35 |
36 |
37 | Link-To-PageC 38 |
39 |
40 | 41 |
42 | Hi React 43 |
44 |
); 45 | } 46 | 47 | class App extends Component { 48 | render() { 49 | return ( 50 |
51 | 52 | 53 | 54 | 55 | 56 | 60 | 61 | 62 |
63 | ); 64 | } 65 | } 66 | 67 | export default withRouter(App); 68 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /src/component/PageA.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import PageHeader from './lib/PageHeader'; 3 | import {NavLink} from 'react-router-dom' 4 | import '../App.css'; 5 | 6 | class PageA extends Component { 7 | constructor(props) { 8 | super(props) 9 | } 10 | 11 | // 组件装载之后调用 12 | componentDidMount() { 13 | 14 | } 15 | //渲染 16 | render() { 17 | return ( 18 |
19 | 20 |
21 |
22 | Link-To-PageB 23 |
24 |
25 | Link-To-PageC 26 |
27 |
28 |
29 | React Page A 30 |
31 |
); 32 | } 33 | 34 | // 组件被卸载 35 | componentWillUnmount() { 36 | 37 | } 38 | } 39 | 40 | export default PageA; 41 | -------------------------------------------------------------------------------- /src/component/PageB.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import PageHeader from './lib/PageHeader'; 3 | import '../App.css'; 4 | 5 | 6 | class PageB extends Component { 7 | constructor(props) { 8 | super(props) 9 | } 10 | 11 | // 组件装载之后调用 12 | componentDidMount() { 13 | 14 | } 15 | 16 | //渲染 17 | render() { 18 | return ( 19 |
20 | 21 |
22 |
23 |
24 |
25 |
26 |
27 | React Page B 28 |
29 |
); 30 | } 31 | 32 | // 组件被卸载 33 | componentWillUnmount() { 34 | 35 | } 36 | } 37 | 38 | export default PageB; 39 | -------------------------------------------------------------------------------- /src/component/PageC.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import PageHeader from './lib/PageHeader'; 3 | import '../App.css'; 4 | 5 | class PageC extends Component { 6 | constructor(props) { 7 | super(props) 8 | } 9 | 10 | // 组件装载之后调用 11 | componentDidMount() { 12 | 13 | } 14 | 15 | //渲染 16 | render() { 17 | return ( 18 |
19 | 20 |
21 |
22 |
23 |
24 |
25 |
26 | React Page C 27 |
28 |
); 29 | } 30 | 31 | // 组件被卸载 32 | componentWillUnmount() { 33 | 34 | } 35 | } 36 | 37 | export default PageC; 38 | -------------------------------------------------------------------------------- /src/component/lib/PageHeader.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {withRouter} from 'react-router-dom' 3 | 4 | import leftArrow from '../../images/larrow.png'; 5 | 6 | class PageHeader extends Component { 7 | constructor(props) { 8 | super(props) 9 | this.handlePageBack = this.handlePageBack.bind(this); 10 | } 11 | 12 | // 组件装载之后调用 13 | componentDidMount() { 14 | 15 | } 16 | 17 | handlePageBack() { 18 | this.props.history.goBack(); 19 | } 20 | 21 | //渲染 22 | render() { 23 | return ( 24 |
25 |
26 |
27 | 28 | 29 | {this.props.title || ''} 30 |
31 |
32 |
); 33 | } 34 | 35 | // 组件被卸载 36 | componentWillUnmount() { 37 | 38 | } 39 | } 40 | 41 | export default withRouter(PageHeader); 42 | -------------------------------------------------------------------------------- /src/css/lib/common.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by caofk on 2019/3/14. 3 | */ 4 | 5 | /*兼容ios不显示图片问题*/ 6 | img { 7 | content: normal !important 8 | } 9 | 10 | /*兼容ios不显示图片问题*/ 11 | 12 | /*flexbox 常用布局方式*/ 13 | 14 | /*伸缩盒子模型*/ 15 | 16 | .box { 17 | display: -webkit-box; /* 老版本语法: Safari, iOS, Android browser, older WebKit browsers. */ 18 | display: -moz-box; /* 老版本语法: Firefox (buggy) */ 19 | display: -ms-flexbox; /* 混合版本语法: IE 10 */ 20 | display: -webkit-flex; /* 新版本语法: Chrome 21+ */ 21 | display: flex; /* 新版本语法: Opera 12.1, Firefox 22+ */ 22 | -webkit-flex-wrap: nowrap; 23 | -ms-flex-wrap: nowrap; 24 | flex-wrap: nowrap; 25 | } 26 | 27 | /*从左至右*/ 28 | 29 | .box-lr { 30 | -webkit-box-direction: normal; 31 | -webkit-box-orient: horizontal; 32 | -moz-flex-direction: row; 33 | -webkit-flex-direction: row; 34 | flex-direction: row; 35 | } 36 | 37 | /*从右至左*/ 38 | 39 | .box-rl { 40 | -webkit-box-pack: end; 41 | -webkit-box-direction: reverse; 42 | -webkit-box-orient: horizontal; 43 | -moz-flex-direction: row-reverse; 44 | -webkit-flex-direction: row-reverse; 45 | flex-direction: row-reverse; 46 | } 47 | 48 | /*从上至下*/ 49 | 50 | .box-tb { 51 | -webkit-box-direction: normal; 52 | -webkit-box-orient: vertical; 53 | -moz-flex-direction: column; 54 | -webkit-flex-direction: column; 55 | flex-direction: column; 56 | } 57 | 58 | /*从下至上*/ 59 | 60 | .box-bt { 61 | -webkit-box-pack: end; 62 | -webkit-box-direction: reverse; 63 | -webkit-box-orient: vertical; 64 | -moz-flex-direction: column-reverse; 65 | -webkit-flex-direction: column-reverse; 66 | flex-direction: column-reverse; 67 | } 68 | 69 | /*主轴居中*/ 70 | 71 | .box-pack-center { 72 | -webkit-box-pack: center; 73 | -moz-justify-content: center; 74 | -webkit-justify-content: center; 75 | justify-content: center; 76 | } 77 | 78 | /*主轴居左*/ 79 | 80 | .box-pack-start { 81 | -webkit-box-pack: start; 82 | -moz-justify-content: flex-start; 83 | -webkit-justify-content: flex-start; 84 | justify-content: flex-start; 85 | } 86 | 87 | /*主轴居右*/ 88 | 89 | .box-pack-end { 90 | -webkit-box-pack: end; 91 | -moz-justify-content: flex-end; 92 | -webkit-justify-content: flex-end; 93 | justify-content: flex-end; 94 | } 95 | 96 | /*主轴左右不留白*/ 97 | 98 | .box-pack-between { 99 | -webkit-box-pack: justify; 100 | -moz-justify-content: space-between; 101 | -webkit-justify-content: space-between; 102 | justify-content: space-between; 103 | } 104 | 105 | /*主轴左右留白*/ 106 | 107 | .box-pack-between { 108 | -moz-justify-content: space-around; 109 | -webkit-justify-content: space-around; 110 | justify-content: space-around; 111 | } 112 | 113 | /*交叉轴居中对齐*/ 114 | 115 | .box-align-center { 116 | -webkit-box-align: center; 117 | -moz-align-items: center; 118 | -webkit-align-items: center; 119 | align-items: center; 120 | } 121 | 122 | /*交叉轴居左对齐*/ 123 | 124 | .box-align-start { 125 | -webkit-box-align: start; 126 | -moz-align-items: start; 127 | -webkit-align-items: flex-start; 128 | align-items: flex-start; 129 | } 130 | 131 | /*交叉轴居右对齐*/ 132 | 133 | .box-align-end { 134 | -webkit-box-align: end; 135 | -moz-align-items: end; 136 | -webkit-align-items: flex-end; 137 | align-items: flex-end; 138 | } 139 | 140 | /**单个指定的子元素自定义对齐方式,可以不同于其他子元素对齐方式**/ 141 | 142 | /**指定子元素居中对齐**/ 143 | 144 | .self-align-center { 145 | align-self: center; 146 | -webkit-align-self: center; 147 | margin: 0 auto; 148 | } 149 | 150 | /**指定子元素顶部对齐**/ 151 | 152 | .self-align-start { 153 | align-self: flex-start; 154 | -webkit-align-self: flex-start; 155 | } 156 | 157 | /**指定子元素底部对齐**/ 158 | 159 | .self-align-end { 160 | align-self: flex-end; 161 | -webkit-align-self: flex-end; 162 | } 163 | 164 | /**指定子元素拉伸**/ 165 | 166 | .self-align-stretch { 167 | align-self: stretch; 168 | -webkit-align-self: stretch; 169 | } 170 | 171 | /**子元素换行**/ 172 | 173 | .box-wrap { 174 | -webkit-flex-wrap: wrap; 175 | -ms-flex-wrap: wrap; 176 | flex-wrap: wrap; 177 | } 178 | 179 | /**子元素不换行**/ 180 | 181 | .box-nowrap { 182 | -webkit-flex-wrap: nowrap; 183 | -ms-flex-wrap: nowrap; 184 | flex-wrap: nowrap; 185 | } 186 | 187 | /*允许子元素伸展(1倍)*/ 188 | 189 | .flex { 190 | -moz-flex-grow: 1; 191 | -webkit-flex-grow: 1; 192 | flex-grow: 1; 193 | } 194 | 195 | /*允许子元素收缩(1倍)*/ 196 | 197 | .shrink { 198 | -moz-flex-shrink: 1; 199 | -webkit-flex-shrink: 1; 200 | flex-shrink: 1; 201 | } 202 | 203 | /**水平居中*/ 204 | 205 | .box-center-center { 206 | display: -webkit-box; 207 | -webkit-box-align: center; 208 | -webkit-box-pack: center; 209 | display: -moz-box; 210 | -moz-box-align: center; 211 | -moz-box-pack: center; 212 | text-align: center; 213 | } 214 | 215 | /**垂直居中*/ 216 | 217 | .box-center-center-v { 218 | display: -webkit-box; 219 | -webkit-box-align: center; 220 | -webkit-box-pack: center; 221 | display: -moz-box; 222 | -moz-box-align: center; 223 | -moz-box-pack: center; 224 | text-align: center; 225 | -webkit-box-orient: vertical; 226 | -moz-box-orient: vertical; 227 | } 228 | 229 | /*flexbox 常用布局方式*/ 230 | 231 | /*1px 解决方案*/ 232 | 233 | @svg square { 234 | @rect { 235 | fill: var(--color, white); 236 | width: 100%; 237 | height: 50%; 238 | } 239 | } 240 | 241 | .example-line { 242 | width: 100%; 243 | background: white svg(square param(--color #E6E6E6)); 244 | background-size: 100% 1px; 245 | background-repeat: no-repeat; 246 | background-position: bottom left; 247 | } 248 | 249 | /*1px 解决方案*/ 250 | 251 | /*伪元素1px*/ 252 | 253 | .row-cell:before { 254 | content: " "; 255 | position: absolute; 256 | left: 0; 257 | top: 0; 258 | right: 0; 259 | height: 1px; 260 | border-top: 1px solid #e5e5e5; 261 | color: #e5e5e5; 262 | -webkit-transform-origin: 0 0; 263 | transform-origin: 0 0; 264 | -webkit-transform: scaleY(0.5); 265 | transform: scaleY(0.5); 266 | left: 15px; 267 | z-index: 2; 268 | } 269 | 270 | .row-cell-64D26D:before { 271 | content: " "; 272 | position: absolute; 273 | left: 0; 274 | bottom: -1px; 275 | right: 0; 276 | height: 1px; 277 | border-top: 1px solid #64D26D;; 278 | color: #64D26D; 279 | -webkit-transform-origin: 0 0; 280 | transform-origin: 0 0; 281 | -webkit-transform: scaleY(0.5); 282 | transform: scaleY(0.5); 283 | z-index: 2; 284 | } 285 | /*伪元素1px*/ 286 | .margin-page-header { 287 | margin-top: 96px; 288 | } 289 | -------------------------------------------------------------------------------- /src/css/lib/pageheader.css: -------------------------------------------------------------------------------- 1 | 2 | /*nav 导航栏*/ 3 | 4 | .nav { 5 | position: fixed; 6 | z-index: 100; 7 | top: 0; 8 | left: 0; 9 | background: #1890ff; 10 | height: 96px; 11 | width: 100%; 12 | } 13 | .nav-line { 14 | border: 1px solid white; 15 | height: 44px; 16 | width: 2px; 17 | margin-right: 22px; 18 | margin-left: 34px; 19 | border-right: 0; 20 | border-bottom: 0; 21 | border-top: 0; 22 | } 23 | 24 | .left-arrow { 25 | width: 30px; 26 | height: 30px; 27 | margin-left: 33px; 28 | } 29 | 30 | .nav-text-min-width { 31 | min-width: 360px; 32 | } 33 | 34 | .nav-left-text { 35 | font-family: DroidSansFallback; 36 | font-size: 32px; 37 | color: #FFFFFF; 38 | line-height: 32px; 39 | } 40 | 41 | .right-text { 42 | font-family: PingFangSC-Regular; 43 | font-size: 26px; 44 | color: #FFFFFF; 45 | letter-spacing: 1px; 46 | text-align: right; 47 | line-height: 34px; 48 | position: absolute; 49 | height: 34px; 50 | right: 24px; 51 | top: 50%; 52 | margin-top: -17px; 53 | } 54 | 55 | /*nav 导航栏*/ 56 | -------------------------------------------------------------------------------- /src/css/lib/reset.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v2.0 | 20110126 3 | License: none (public domain) 4 | */ 5 | 6 | html, body, div, span, applet, object, iframe, 7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 8 | a, abbr, acronym, address, big, cite, code, 9 | del, dfn, em, img, ins, kbd, q, s, samp, 10 | small, strike, strong, sub, sup, tt, var, 11 | b, u, i, center, 12 | dl, dt, dd, ol, ul, li, 13 | fieldset, form, label, legend, 14 | table, caption, tbody, tfoot, thead, tr, th, td, 15 | article, aside, canvas, details, embed, 16 | figure, figcaption, footer, header, hgroup, 17 | menu, nav, output, ruby, section, summary, 18 | time, mark, audio, video { 19 | margin: 0; 20 | padding: 0; 21 | border: 0; 22 | font-size: 100%; 23 | font: inherit; 24 | vertical-align: baseline; 25 | } 26 | /* HTML5 display-role reset for older browsers */ 27 | article, aside, details, figcaption, figure, 28 | footer, header, hgroup, menu, nav, section { 29 | display: block; 30 | } 31 | body { 32 | line-height: 1; 33 | } 34 | ol, ul { 35 | list-style: none; 36 | } 37 | blockquote, q { 38 | quotes: none; 39 | } 40 | blockquote:before, blockquote:after, 41 | q:before, q:after { 42 | content: ''; 43 | content: none; 44 | } 45 | table { 46 | border-collapse: collapse; 47 | border-spacing: 0; 48 | } 49 | -------------------------------------------------------------------------------- /src/images/WX20190325-003909@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caoxiaoke/react-vw-layout/0e0080358a006dcba7283c89c4fa9045110423b5/src/images/WX20190325-003909@2x.png -------------------------------------------------------------------------------- /src/images/WX20190325-005512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caoxiaoke/react-vw-layout/0e0080358a006dcba7283c89c4fa9045110423b5/src/images/WX20190325-005512@2x.png -------------------------------------------------------------------------------- /src/images/WX20190325-005603@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caoxiaoke/react-vw-layout/0e0080358a006dcba7283c89c4fa9045110423b5/src/images/WX20190325-005603@2x.png -------------------------------------------------------------------------------- /src/images/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caoxiaoke/react-vw-layout/0e0080358a006dcba7283c89c4fa9045110423b5/src/images/home.png -------------------------------------------------------------------------------- /src/images/larrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caoxiaoke/react-vw-layout/0e0080358a006dcba7283c89c4fa9045110423b5/src/images/larrow.png -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 5 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 6 | sans-serif; 7 | -webkit-font-smoothing: antialiased; 8 | -moz-osx-font-smoothing: grayscale; 9 | } 10 | 11 | code { 12 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 13 | monospace; 14 | } 15 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import {BrowserRouter} from 'react-router-dom' 6 | 7 | import * as serviceWorker from './serviceWorker'; 8 | 9 | ReactDOM.render( 10 | 11 | 12 | , 13 | document.getElementById('root') 14 | ); 15 | 16 | // If you want your app to work offline and load faster, you can change 17 | // unregister() to register() below. Note this comes with some pitfalls. 18 | // Learn more about service workers: https://bit.ly/CRA-PWA 19 | serviceWorker.unregister(); 20 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.1/8 is considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl) 104 | .then(response => { 105 | // Ensure service worker exists, and that we really are getting a JS file. 106 | const contentType = response.headers.get('content-type'); 107 | if ( 108 | response.status === 404 || 109 | (contentType != null && contentType.indexOf('javascript') === -1) 110 | ) { 111 | // No service worker found. Probably a different app. Reload the page. 112 | navigator.serviceWorker.ready.then(registration => { 113 | registration.unregister().then(() => { 114 | window.location.reload(); 115 | }); 116 | }); 117 | } else { 118 | // Service worker found. Proceed as normal. 119 | registerValidSW(swUrl, config); 120 | } 121 | }) 122 | .catch(() => { 123 | console.log( 124 | 'No internet connection found. App is running in offline mode.' 125 | ); 126 | }); 127 | } 128 | 129 | export function unregister() { 130 | if ('serviceWorker' in navigator) { 131 | navigator.serviceWorker.ready.then(registration => { 132 | registration.unregister(); 133 | }); 134 | } 135 | } 136 | --------------------------------------------------------------------------------