├── src ├── entry.js ├── scss │ ├── poster.scss │ ├── theme │ │ └── _color.scss │ ├── animation │ │ ├── _selector.scss │ │ └── _loading.scss │ ├── base │ │ └── _base.scss │ └── components │ │ ├── _loading.scss │ │ ├── _editor.scss │ │ ├── _selector.scss │ │ └── _result.scss └── js │ ├── loading.js │ ├── config.js │ ├── poster.js │ ├── selector.js │ └── editor.js ├── dist └── poster │ ├── qr.jpg │ ├── item.png │ ├── footer.png │ ├── qr-stuq.jpg │ ├── title-1.png │ ├── title-2.png │ ├── characters │ ├── 01.png │ ├── 02.png │ ├── 03.png │ ├── 04.png │ ├── 05.png │ ├── 06.png │ ├── 07.png │ ├── 08.png │ ├── 09.png │ ├── 10.png │ ├── 11.png │ ├── 12.png │ ├── 13.png │ ├── 14.png │ ├── 15.png │ ├── 16.png │ └── 17.png │ └── icons.svg ├── screenshoot ├── poster-1.png ├── poster-2.png ├── poster-3.png ├── poster-4.png └── poster-5.png ├── jade ├── index.jade └── detail.jade ├── index.html ├── .gitignore ├── package.json ├── LICENSE ├── webpack.config.js ├── README.md └── detail.html /src/entry.js: -------------------------------------------------------------------------------- 1 | require('./scss/poster.scss'); 2 | require('./js/poster.js'); -------------------------------------------------------------------------------- /dist/poster/qr.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamStuQ/Poster/HEAD/dist/poster/qr.jpg -------------------------------------------------------------------------------- /dist/poster/item.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamStuQ/Poster/HEAD/dist/poster/item.png -------------------------------------------------------------------------------- /dist/poster/footer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamStuQ/Poster/HEAD/dist/poster/footer.png -------------------------------------------------------------------------------- /dist/poster/qr-stuq.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamStuQ/Poster/HEAD/dist/poster/qr-stuq.jpg -------------------------------------------------------------------------------- /dist/poster/title-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamStuQ/Poster/HEAD/dist/poster/title-1.png -------------------------------------------------------------------------------- /dist/poster/title-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamStuQ/Poster/HEAD/dist/poster/title-2.png -------------------------------------------------------------------------------- /screenshoot/poster-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamStuQ/Poster/HEAD/screenshoot/poster-1.png -------------------------------------------------------------------------------- /screenshoot/poster-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamStuQ/Poster/HEAD/screenshoot/poster-2.png -------------------------------------------------------------------------------- /screenshoot/poster-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamStuQ/Poster/HEAD/screenshoot/poster-3.png -------------------------------------------------------------------------------- /screenshoot/poster-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamStuQ/Poster/HEAD/screenshoot/poster-4.png -------------------------------------------------------------------------------- /screenshoot/poster-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamStuQ/Poster/HEAD/screenshoot/poster-5.png -------------------------------------------------------------------------------- /dist/poster/characters/01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamStuQ/Poster/HEAD/dist/poster/characters/01.png -------------------------------------------------------------------------------- /dist/poster/characters/02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamStuQ/Poster/HEAD/dist/poster/characters/02.png -------------------------------------------------------------------------------- /dist/poster/characters/03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamStuQ/Poster/HEAD/dist/poster/characters/03.png -------------------------------------------------------------------------------- /dist/poster/characters/04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamStuQ/Poster/HEAD/dist/poster/characters/04.png -------------------------------------------------------------------------------- /dist/poster/characters/05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamStuQ/Poster/HEAD/dist/poster/characters/05.png -------------------------------------------------------------------------------- /dist/poster/characters/06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamStuQ/Poster/HEAD/dist/poster/characters/06.png -------------------------------------------------------------------------------- /dist/poster/characters/07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamStuQ/Poster/HEAD/dist/poster/characters/07.png -------------------------------------------------------------------------------- /dist/poster/characters/08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamStuQ/Poster/HEAD/dist/poster/characters/08.png -------------------------------------------------------------------------------- /dist/poster/characters/09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamStuQ/Poster/HEAD/dist/poster/characters/09.png -------------------------------------------------------------------------------- /dist/poster/characters/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamStuQ/Poster/HEAD/dist/poster/characters/10.png -------------------------------------------------------------------------------- /dist/poster/characters/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamStuQ/Poster/HEAD/dist/poster/characters/11.png -------------------------------------------------------------------------------- /dist/poster/characters/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamStuQ/Poster/HEAD/dist/poster/characters/12.png -------------------------------------------------------------------------------- /dist/poster/characters/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamStuQ/Poster/HEAD/dist/poster/characters/13.png -------------------------------------------------------------------------------- /dist/poster/characters/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamStuQ/Poster/HEAD/dist/poster/characters/14.png -------------------------------------------------------------------------------- /dist/poster/characters/15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamStuQ/Poster/HEAD/dist/poster/characters/15.png -------------------------------------------------------------------------------- /dist/poster/characters/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamStuQ/Poster/HEAD/dist/poster/characters/16.png -------------------------------------------------------------------------------- /dist/poster/characters/17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamStuQ/Poster/HEAD/dist/poster/characters/17.png -------------------------------------------------------------------------------- /src/scss/poster.scss: -------------------------------------------------------------------------------- 1 | @charset 'utf-8'; 2 | 3 | // Theme 4 | @import 'theme/color'; 5 | 6 | // Base 7 | @import 'base/base'; 8 | 9 | // Animation 10 | @import 'animation/loading'; 11 | @import 'animation/selector'; 12 | 13 | // Components 14 | @import 'components/loading'; 15 | @import 'components/selector'; 16 | @import 'components/editor'; 17 | @import 'components/result'; -------------------------------------------------------------------------------- /src/scss/theme/_color.scss: -------------------------------------------------------------------------------- 1 | // color.scss 2 | 3 | $white: #fff; 4 | $black: #000; 5 | $blue: #245c9f; 6 | $blue-a: rgba(36, 92, 159, 1); 7 | $blue-n: rgba(36, 92, 159, 0); 8 | $dgray: #727171; 9 | $lgray: #e5e5e5; 10 | 11 | $bar-color: #008cd6; 12 | $ball-color: #00b7ed; 13 | 14 | $sblue: #eff5fc; 15 | $syellow: #fef9e8; 16 | $sgreen: #ebf6f5; 17 | $spink: #fdf2f8; 18 | 19 | $border-gray: #b4b5b5; -------------------------------------------------------------------------------- /jade/index.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | meta(charset='utf-8') 5 | meta(name='viewport' content='width=device-width, initial-scale=1, shrink-to-fit=no, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, target-densitydpi=device-dpi') 6 | title 海报生成器 7 | body 8 | div.bg 9 | svg 10 | use(xlink:href='/dist/poster/icons.svg#as-mountain') 11 | #poster 12 | script(src='/dist/poster.min.js') -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 海报生成器 7 | 8 | 9 |
10 | 11 | 12 | 13 |
14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /src/scss/animation/_selector.scss: -------------------------------------------------------------------------------- 1 | @keyframes slideUp { 2 | 0% { 3 | transform: translateY(12px); 4 | } 5 | 10% { 6 | transform: translateY(10px); 7 | } 8 | 20% { 9 | transform: translateY(8px); 10 | } 11 | 30% { 12 | transform: translateY(6px); 13 | } 14 | 40% { 15 | transform: translateY(4px); 16 | } 17 | 50% { 18 | transform: translateY(2px); 19 | } 20 | 60% { 21 | transform: translateY(-2px); 22 | } 23 | 70% { 24 | transform: translateY(-4px); 25 | } 26 | 80% { 27 | transform: translateY(-6px); 28 | } 29 | 90% { 30 | transform: translateY(-8px); 31 | } 32 | 100% { 33 | transform: translateY(-10px); 34 | } 35 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | -------------------------------------------------------------------------------- /src/js/loading.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default class extends React.Component { 4 | render() { 5 | return ( 6 |
7 | 11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | 20 | 21 | 22 |
23 | ); 24 | } 25 | } -------------------------------------------------------------------------------- /src/scss/base/_base.scss: -------------------------------------------------------------------------------- 1 | // base.scss 2 | 3 | * { 4 | box-sizing: border-box; 5 | } 6 | 7 | body { 8 | display: block; 9 | position: relative; 10 | width: 100%; 11 | margin: 0; 12 | padding: 0; 13 | font-family: "Helvetica Neue", Helvetica, Arial, "Lantinghei SC", "Hiragino Sans GB", STHeiti, "Microsoft Yahei", "Microsoft Sans Serif", sans-serif; 14 | font-size: 16px; 15 | overflow: hidden; 16 | } 17 | 18 | .poster { 19 | display: block; 20 | position: relative; 21 | width: 100%; 22 | } 23 | 24 | .bg { 25 | display: block; 26 | position: absolute; 27 | width: 100%; 28 | height: 100vh; 29 | top: 0; 30 | z-index: -1; 31 | opacity: 0.1; 32 | overflow: hidden; 33 | 34 | svg { 35 | position: absolute; 36 | bottom: 0; 37 | left: -50%; 38 | width: 300vw; 39 | height: 124vw; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Poster", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "entry.js", 6 | "dependencies": { 7 | "babel-core": "^6.9.1", 8 | "babel-loader": "^6.2.4", 9 | "babel-preset-es2015": "^6.9.0", 10 | "babel-preset-react": "^6.5.0", 11 | "css-loader": "^0.23.1", 12 | "jquery": "^3.0.0", 13 | "node-sass": "^3.7.0", 14 | "postcss": "^5.0.21", 15 | "postcss-loader": "^0.9.1", 16 | "precss": "^1.4.0", 17 | "react": "^15.1.0", 18 | "react-dom": "^15.1.0", 19 | "sass-loader": "^3.2.0", 20 | "style-loader": "^0.13.1", 21 | "webpack": "^1.13.1" 22 | }, 23 | "devDependencies": { 24 | "fullpage.js": "^2.8.1", 25 | "swiper": "^3.3.1" 26 | }, 27 | "scripts": { 28 | "test": "echo \"Error: no test specified\" && exit 1" 29 | }, 30 | "author": "Steve Zhang", 31 | "license": "MIT" 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (C) 2016 Steve Zhang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require("webpack"), 2 | autoprefixer = require('autoprefixer'), 3 | precss = require('precss'); 4 | 5 | module.exports = { 6 | context: __dirname, 7 | 8 | entry: './src/entry', 9 | 10 | output: { 11 | path: './dist', 12 | filename: 'poster.min.js' 13 | }, 14 | 15 | devtool: 'source-map', 16 | 17 | module: { 18 | loaders: [ 19 | { 20 | test: /\.scss$/, 21 | loaders: [ 22 | 'style', 23 | 'css?sourceMap', 24 | 'postcss-loader', 25 | 'sass?sourceMap' 26 | ] 27 | }, 28 | 29 | { 30 | test: /\.css$/, 31 | loaders: [ 32 | 'style', 33 | 'css?sourceMap', 34 | 'postcss-loader' 35 | ] 36 | }, 37 | 38 | { 39 | test: /\.js?$/, 40 | exclude: /(node_modules|bower_components)/, 41 | loader: 'babel', 42 | query: { 43 | presets: [ 44 | 'react', 45 | 'es2015' 46 | ] 47 | } 48 | } 49 | ] 50 | }, 51 | 52 | postcss: () => { 53 | return [autoprefixer, precss]; 54 | }, 55 | 56 | plugins: [ 57 | new webpack.optimize.UglifyJsPlugin({ 58 | compress: { 59 | warnings: false 60 | } 61 | }) 62 | ] 63 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 海报生成器 2 | 3 | 使用 <canvas> 的**[海报生成器](http://www.geekbang.org/poster)**。 4 | 5 | *注:页面主要为手机端设计,在电脑浏览器中打开时,请缩小浏览器宽高比至手机竖屏比例。* 6 | 7 | ## 构建 8 | 9 | React + ES6 + SCSS + Webpack 10 | 11 | ## 使用 12 | 1.克隆项目至本地环境并进入项目目录: 13 | 14 | ```sh 15 | git clone https://github.com/TeamStuQ/Poster 16 | cd Poster 17 | ``` 18 | 19 | 2.安装相关的依赖包(请确保你已经安装了[node.js](https://nodej.org/)): 20 | 21 | ```sh 22 | npm install 23 | ``` 24 | 25 | 3.如果需要,在全局环境下安装 [webpack](https://github.com/webpak/webpack): 26 | 27 | ```sh 28 | npm install wepack -g 29 | ``` 30 | 31 | 4.将项目的源代码(位于 src/ 下)编译至生产所需的目标码(位于 dist/ 下): 32 | 33 | ```sh 34 | webpack -w 35 | ``` 36 | 37 | webpack 使用说明请参考 [https://webpack.github.io/docs/](https://webpack.gihub.io/docs/)。 38 | 39 | 5.效果预览,请在项目的根目录下开启 http 服务器,推荐使用 [webpack-dev-server](https://github.com/webpack/wbpack-dev-server) 40 | 41 | ```sh 42 | npm install webpack-dev-server -g 43 | webpck-dev-server 44 | ``` 45 | 46 | 然后在浏览器中访问 [http://localhost:8080/](http://localhost:8080/) 即可。 47 | 48 | **注:本项目不包含后端代码及其实现。** 49 | 50 | ## 截图 51 | 52 | ![](screenshoot/poster-1.png) 53 | 54 | ![](screenshoot/poster-2.png) 55 | 56 | ![](screenshoot/poster-3.png) 57 | 58 | ![](screenshoot/poster-4.png) 59 | 60 | ![](screenshoot/poster-5.png) 61 | 62 | ## 其他 63 | 64 | 本项目中使用到的其他开源项目: 65 | 66 | * [fullpage.js](https://github.com/alvarotrigo/fullPage.js/) 67 | 68 | * [swiper](https://github.com/nolimits4web/swiper/) 69 | 70 | * [CSS 动画](https://codepen.io/ispal/pen/mVaaJe) 71 | -------------------------------------------------------------------------------- /src/scss/components/_loading.scss: -------------------------------------------------------------------------------- 1 | // _loading.scss 2 | 3 | .loading { 4 | display: block; 5 | position: relative; 6 | width: 100%; 7 | height: 100vh; 8 | padding-top: 20vh; 9 | 10 | ul { 11 | display: block; 12 | position: relative; 13 | width: 100%; 14 | margin: 0; 15 | padding: 0; 16 | list-style: none; 17 | 18 | li { 19 | display: block; 20 | position: relative; 21 | width: 100%; 22 | text-align: center; 23 | font-size: 1rem; 24 | font-weight: 400; 25 | line-height: 1em; 26 | margin-bottom: 0.75rem; 27 | color: $dgray; 28 | } 29 | } 30 | 31 | .loader { 32 | display: flex; 33 | position: relative; 34 | margin: -2rem auto 0; 35 | align-items: center; 36 | justify-content: center; 37 | position: relative; 38 | width: 75px; 39 | height: 100px; 40 | transform: scale(0.5); 41 | 42 | &__bar { 43 | position: absolute; 44 | bottom: 0; 45 | width: 10px; 46 | height: 50%; 47 | background: $bar-color; 48 | transform-origin: center bottom; 49 | box-shadow: 1px 1px 0 rgba(0,0,0,.2); 50 | 51 | @for $i from 1 through 5 { 52 | &:nth-child(#{$i}) { 53 | left: ($i - 1) * 15px; 54 | transform: scale(1,$i*.2); 55 | animation: barUp#{$i} 4s infinite; 56 | } 57 | } 58 | 59 | } 60 | 61 | &__ball { 62 | position: absolute; 63 | bottom: 10px; 64 | left: 0; 65 | width: 10px; 66 | height: 10px; 67 | background: $ball-color; 68 | border-radius: 50%; 69 | animation: ball 4s infinite; 70 | } 71 | } 72 | 73 | .as-logo { 74 | display: block; 75 | position: absolute; 76 | width: 30vw; 77 | height: 9.3vw; 78 | bottom: 12vh; 79 | left: 35vw; 80 | } 81 | } -------------------------------------------------------------------------------- /jade/detail.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | meta(charset='utf-8') 5 | meta(name='viewport' content='width=device-width, initial-scale=1, shrink-to-fit=no, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, target-densitydpi=device-dpi') 6 | title 我是{{ poster.data.position }}!| {{ poster.data.slogan }} 7 | body 8 | div.result 9 | div.section.show 10 | img.photo(alt='photo' src='{{ poster.image }}' id='photo') 11 | div.choose 12 | a#download 下载 13 | a#new 创建我的专属套路 14 | a#share 分享 15 | div.next 16 | span 更多套路 17 | div.next-slide 18 | svg 19 | use(xlink:href='/dist/poster/icons.svg#up') 20 | svg 21 | use(xlink:href='/dist/poster/icons.svg#up') 22 | div.section.more 23 | div.to To You: 24 | ul.sentences 25 | li 长按识别图中二维码 26 | li 关注<ArchSummit技术关注> 27 | li 回复“套路” 28 | li 看看其他程序员隐藏的文艺之力! 29 | div.from From 官方套路 30 | img.qr(alt='qrcode' src='/dist/poster/qr.jpg') 31 | div.next 32 | span 下方高能 33 | div.next-slide 34 | svg 35 | use(xlink:href='/dist/poster/icons.svg#up') 36 | svg 37 | use(xlink:href='/dist/poster/icons.svg#up') 38 | div.section.more 39 | div.to 哇,你发现了这个彩蛋: 40 | ul.sentences 41 | li 长按识别图中二维码 42 | li 关注<StuQ订阅号> 43 | li 获得全套“StuQ职业技术角色技能图谱” 44 | li 还有,你一定会想要的“StuQ课程优惠码” 45 | div.from From stuq.org(读音:斯达Q) 46 | img.qr(alt='qrcode' src='/dist/poster/qr-stuq.jpg') 47 | div.download-info 48 | div.close 49 | svg 50 | use(xlink:href='/dist/poster/icons.svg#download') 51 | div.info 长按保存图片 52 | div.info 让大家感受你的文艺之力吧 53 | div.share-info 54 | div.close 55 | svg 56 | use(xlink:href='/dist/poster/icons.svg#share') 57 | div.info 点击右上角分享 58 | div.info 发现程序员朋友的另一面 59 | div.bg 60 | svg 61 | use(xlink:href='/dist/poster/icons.svg#as-mountain') 62 | script(src='/dist/poster.min.js') -------------------------------------------------------------------------------- /src/scss/animation/_loading.scss: -------------------------------------------------------------------------------- 1 | @keyframes ball { 2 | 0% { 3 | transform: translate(0, 0); 4 | } 5 | 5% { 6 | transform: translate(8px, -14px); 7 | } 8 | 10% { 9 | transform: translate(15px, -10px); 10 | } 11 | 17% { 12 | transform: translate(23px, -24px); 13 | } 14 | 20% { 15 | transform: translate(30px, -20px); 16 | } 17 | 27% { 18 | transform: translate(38px, -34px); 19 | } 20 | 30% { 21 | transform: translate(45px, -30px) 22 | } 23 | 37% { 24 | transform: translate(53px, -44px) 25 | } 26 | 40% { 27 | transform: translate(60px, -40px) 28 | } 29 | 50% { 30 | transform: translate(60px, 0) 31 | } 32 | 57% { 33 | transform: translate(53px, -14px) 34 | } 35 | 60% { 36 | transform: translate(45px, -10px) 37 | } 38 | 67% { 39 | transform: translate(37px, -24px) 40 | } 41 | 70% { 42 | transform: translate(30px, -20px) 43 | } 44 | 77% { 45 | transform: translate(22px, -34px) 46 | } 47 | 80% { 48 | transform: translate(15px, -30px) 49 | } 50 | 87% { 51 | transform: translate(7px, -44px) 52 | } 53 | 90% { 54 | transform: translate(0, -40px) 55 | } 56 | 100% { 57 | transform: translate(0, 0); 58 | } 59 | } 60 | 61 | @keyframes barUp1 { 62 | 0% { 63 | transform: scale(1, .2); 64 | } 65 | 40%{ 66 | transform: scale(1, .2); 67 | } 68 | 50% { 69 | transform: scale(1, 1); 70 | } 71 | 90% { 72 | transform: scale(1,1); 73 | } 74 | 100% { 75 | transform: scale(1,.2); 76 | } 77 | } 78 | @keyframes barUp2 { 79 | 0% { 80 | transform: scale(1, .4); 81 | } 82 | 40% { 83 | transform: scale(1, .4); 84 | } 85 | 50% { 86 | transform: scale(1, .8); 87 | } 88 | 90% { 89 | transform: scale(1, .8); 90 | } 91 | 100% { 92 | transform: scale(1, .4); 93 | } 94 | } 95 | @keyframes barUp3 { 96 | 0% { 97 | transform: scale(1, .6); 98 | } 99 | 100% { 100 | transform: scale(1, .6); 101 | } 102 | } 103 | @keyframes barUp4 { 104 | 0% { 105 | transform: scale(1, .8); 106 | } 107 | 40% { 108 | transform: scale(1, .8); 109 | } 110 | 50% { 111 | transform: scale(1, .4); 112 | } 113 | 90% { 114 | transform: scale(1, .4); 115 | } 116 | 100% { 117 | transform: scale(1, .8); 118 | } 119 | } 120 | @keyframes barUp5 { 121 | 0% { 122 | transform: scale(1, 1); 123 | } 124 | 40% { 125 | transform: scale(1, 1); 126 | } 127 | 50% { 128 | transform: scale(1, .2); 129 | } 130 | 90% { 131 | transform: scale(1, .2); 132 | } 133 | 100% { 134 | transform: scale(1, 1); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /detail.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 我是{{ poster.data.position }}!| {{ poster.data.slogan }} 7 | 8 | 9 |
10 |
photo 11 | 12 | 22 |
23 |
24 |
To You:
25 | 31 |
From 官方套路
qrcode 32 | 42 |
43 |
44 |
哇,你发现了这个彩蛋:
45 | 51 |
From stuq.org(读音:斯达Q)
qrcode 52 |
53 |
54 |
55 |
56 | 57 | 58 | 59 |
长按保存图片
60 |
让大家感受你的文艺之力吧
61 |
62 |
63 |
64 | 65 | 66 | 67 |
点击右上角分享
68 |
发现程序员朋友的另一面
69 |
70 |
71 | 72 | 73 | 74 |
75 | 76 | 77 | -------------------------------------------------------------------------------- /src/js/config.js: -------------------------------------------------------------------------------- 1 | export const posLimit = 7; 2 | 3 | export const sloganLimit = 16; 4 | 5 | export const posterSize = { 6 | 'width': 750, 7 | 'height': 1334, 8 | 'mainHe': 1151 9 | }; 10 | 11 | export const photoSize = { 12 | 'width': 750, 13 | 'height': 659 14 | }; 15 | 16 | export const photoTitle1 = '/dist/poster/title-1.png'; 17 | 18 | export const photoTitle2 = '/dist/poster/title-2.png'; 19 | 20 | export const sloganArrow = '/dist/poster/item.png'; 21 | 22 | export const footer = '/dist/poster/footer.png'; 23 | 24 | export const diyName = '???'; 25 | 26 | export const themeColors = { 27 | 'white' : '#ffffff', 28 | 'blue' : '#eff5fc', 29 | 'green' : '#ebf6f5', 30 | 'yellow': '#fef9e8', 31 | 'pink' : '#fdf2f8' 32 | }; 33 | 34 | export const imgData = [ 35 | { 36 | name : '架构师', 37 | src : '/dist/poster/characters/01.png', 38 | slogan1: '有问题?重构!', 39 | slogan2: '不可能出现这种情况的啊?!', 40 | slogan3: '一定不会是我的原因,可能是硬件问题?' 41 | }, 42 | { 43 | name : '前端工程师', 44 | src : '/dist/poster/characters/02.png', 45 | slogan1: '这不是bug,肯定是网络问题', 46 | slogan2: '你忘记清除缓存了,兄弟', 47 | slogan3: '兼容IE6?' 48 | }, 49 | { 50 | name : 'PHP工程师', 51 | src : '/dist/poster/characters/03.png', 52 | slogan1: '这是字符编码的问题', 53 | slogan2: '等我下班后再跟你细说…', 54 | slogan3: '我一定要说服他们,PHP是最好的语言' 55 | }, 56 | { 57 | name : '测试工程师', 58 | src : '/dist/poster/characters/04.png', 59 | slogan1: '我会在代码更替的时候添加单元测试', 60 | slogan2: '我检查过一遍了,别怕,上线吧!', 61 | slogan3: '我又不能测试所有的功能' 62 | }, 63 | { 64 | name : '安卓工程师', 65 | src : '/dist/poster/characters/05.png', 66 | slogan1: '是不是你硬件出错了', 67 | slogan2: '老板,我们要买新的Android 手机', 68 | slogan3: '昨天明明正常,不知道为啥现在就不行了' 69 | }, 70 | { 71 | name : 'iOS工程师', 72 | src : '/dist/poster/characters/06.png', 73 | slogan1: 'WWDC: World Wide Developer Crying', 74 | slogan2: '我的App评级为17+岁,你呢?', 75 | slogan3: '你是用黑苹果写的吧?' 76 | }, 77 | { 78 | name : '运维工程师', 79 | src : '/dist/poster/characters/07.png', 80 | slogan1: '我们的预算还够吧?', 81 | slogan2: '我不是网管,也不是修电脑的!', 82 | slogan3: '入门运维工程狮最重要的要求是体力充沛' 83 | }, 84 | { 85 | name : '安全工程师', 86 | src : '/dist/poster/characters/08.png', 87 | slogan1: '你是不是改了什么,你重演一下我看看', 88 | slogan2: '之前一直都没有出现过这种情况啊', 89 | slogan3: '我真不会写外挂呀' 90 | }, 91 | { 92 | name : '全栈工程师', 93 | src : '/dist/poster/characters/09.png', 94 | slogan1: '只是改一行代码,不会对程序造成影响的!', 95 | slogan2: '这些代码是上一个开发者写的,不是我写的', 96 | slogan3: '没办法,这是一个公认的bug' 97 | }, 98 | { 99 | name : '大数据工程师', 100 | src : '/dist/poster/characters/10.png', 101 | slogan1: '我们不考虑个例', 102 | slogan2: '快了,数据已经跑了90%了', 103 | slogan3: '大数据显示:我们组男女比例是40比0' 104 | }, 105 | { 106 | name : '云计算工程师', 107 | src : '/dist/poster/characters/11.png', 108 | slogan1: '不用担心,这次肯定不会有问题了', 109 | slogan2: '我觉得写的很清楚啊,为什么你说看不懂', 110 | slogan3: '只改一行代码,不会造成影响的,放心' 111 | }, 112 | { 113 | name : '机器学习工程师', 114 | src : '/dist/poster/characters/12.png', 115 | slogan1: '不就是算法吗,坐下来我给你讲', 116 | slogan2: '已经做好了,但还有一些细节要调一下', 117 | slogan3: '这个很简单的,我一个礼拜就能完成了' 118 | }, 119 | { 120 | name : '嵌入式工程师', 121 | src : '/dist/poster/characters/13.png', 122 | slogan1: '这个任务很容易啊!', 123 | slogan2: '再给我两天,保证能做好', 124 | slogan3: '这只是暂时的方案,在正式版我会修改的' 125 | }, 126 | { 127 | name : '智能硬件工程师', 128 | src : '/dist/poster/characters/14.png', 129 | slogan1: '我以后会补上注释和文档的', 130 | slogan2: '在我机器上好好的啊!', 131 | slogan3: '第一次出现这样的问题啊,我都没见过' 132 | }, 133 | { 134 | name : 'CTO', 135 | src : '/dist/poster/characters/15.png', 136 | slogan1: '这个功能我们在下个版本修正', 137 | slogan2: '我们开会review代码吧', 138 | slogan3: '我们缺人啊!' 139 | }, 140 | { 141 | name : '产品经理', 142 | src : '/dist/poster/characters/16.png', 143 | slogan1: '这是老板的需求', 144 | slogan2: '这是总监的需求', 145 | slogan3: '反正这不是我的需求' 146 | }, 147 | { 148 | name : '设计师', 149 | src : '/dist/poster/characters/17.png', 150 | slogan1: '什么叫大气?', 151 | slogan2: '什么叫大气…', 152 | slogan3: '什么叫大气!' 153 | } 154 | ]; 155 | -------------------------------------------------------------------------------- /src/scss/components/_editor.scss: -------------------------------------------------------------------------------- 1 | .editor { 2 | display: block; 3 | position: relative; 4 | width: 100%; 5 | height: 100vh; 6 | padding: 5vh 6.4vw; 7 | z-index: 10; 8 | 9 | .poster-bg { 10 | display: none; 11 | position: absolute; 12 | top: 0; 13 | left: 0; 14 | width: 100vw; 15 | height: 100vh; 16 | background-color: rgba(0, 0, 0, 0.3); 17 | z-index: 10; 18 | 19 | &.active { 20 | display: block; 21 | } 22 | } 23 | 24 | .title { 25 | display: block; 26 | position: relative; 27 | width: 100%; 28 | text-align: center; 29 | font: { 30 | size: 5.6vw; 31 | weight: 400; 32 | } 33 | color: $black; 34 | line-height: 1em; 35 | margin-bottom: 8vh; 36 | } 37 | 38 | .limit { 39 | display: block; 40 | position: relative; 41 | width: 100%; 42 | text-align: left; 43 | font: { 44 | size: 3.2vw; 45 | weight: 300; 46 | } 47 | color: $dgray; 48 | line-height: 1em; 49 | margin-bottom: 2vw; 50 | } 51 | 52 | .slogans { 53 | display: block; 54 | position: relative; 55 | width: 100%; 56 | margin: 0 { 57 | bottom: 10.5vh; 58 | } 59 | padding: 0; 60 | list-style: none; 61 | 62 | li { 63 | display: block; 64 | position: relative; 65 | width: 100%; 66 | margin-bottom: 1.5vw; 67 | 68 | &:last-child { 69 | margin-bottom: 0; 70 | } 71 | 72 | input { 73 | display: block; 74 | position: relative; 75 | width: 100%; 76 | height: 12.4vw; 77 | line-height: 12.4vw; 78 | padding: 0 3.6vw; 79 | font: { 80 | size: 4vw; 81 | weight: 400; 82 | } 83 | color: $black; 84 | border: 1px solid $border-gray; 85 | } 86 | } 87 | } 88 | 89 | .colors { 90 | display: flex; 91 | position: relative; 92 | width: 100%; 93 | margin: 0 { 94 | top: 3.5vw; 95 | bottom: 9vw; 96 | } 97 | padding: 0; 98 | list-style: none; 99 | flex { 100 | direction: row; 101 | wrap: wrap; 102 | } 103 | justify-content: space-around; 104 | align-items: stretch; 105 | 106 | li { 107 | display: block; 108 | position: relative; 109 | width: 7.6vw; 110 | height: 7.6vw; 111 | padding: 2vw; 112 | border: 1px solid $border-gray { 113 | radius: 50%; 114 | } 115 | cursor: pointer; 116 | 117 | &.white { 118 | background-color: $white; 119 | } 120 | 121 | &.blue { 122 | background-color: $sblue; 123 | } 124 | 125 | &.green { 126 | background-color: $sgreen; 127 | } 128 | 129 | &.yellow { 130 | background-color: $syellow; 131 | } 132 | 133 | &.pink { 134 | background-color: $spink; 135 | } 136 | 137 | &.active { 138 | cursor: default; 139 | 140 | &:after { 141 | content: ''; 142 | display: block; 143 | position: relative; 144 | width: 100%; 145 | height: 100%; 146 | border: 1px solid $border-gray { 147 | radius: 50%; 148 | } 149 | background-color: $white; 150 | box-sizing: border-box; 151 | } 152 | } 153 | } 154 | } 155 | 156 | .gen { 157 | display: flex; 158 | position: relative; 159 | width: 100%; 160 | margin: 0; 161 | padding: 0; 162 | list-style: none; 163 | flex { 164 | direction: row; 165 | wrap: wrap; 166 | } 167 | justify-content: space-around; 168 | align-items: stretch; 169 | 170 | li { 171 | display: inline-block; 172 | position: relative; 173 | width: 40vw; 174 | height: 12vw; 175 | line-height: 12vw; 176 | text-align: center; 177 | border-radius: 10px; 178 | color: $white; 179 | font: { 180 | size: 4.4vw; 181 | weight: 400; 182 | } 183 | cursor: pointer; 184 | letter-spacing: 0.5em; 185 | 186 | &:first-child { 187 | background-color: $blue; 188 | } 189 | 190 | &:last-child { 191 | background-color: $dgray; 192 | } 193 | 194 | &.forbidden { 195 | display: none; 196 | } 197 | } 198 | } 199 | 200 | .poster-canvas { 201 | display: none; 202 | position: absolute; 203 | width: 80vw; 204 | top: 5vw; 205 | left: 10vw; 206 | z-index: 11; 207 | 208 | &.active { 209 | display: block; 210 | } 211 | } 212 | } -------------------------------------------------------------------------------- /src/js/poster.js: -------------------------------------------------------------------------------- 1 | import $ from 'jquery'; 2 | import React from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | import fullpage from 'fullpage.js'; 5 | 6 | import { 7 | posLimit, 8 | sloganLimit, 9 | posterSize, 10 | photoSize, 11 | photoTitle1, 12 | photoTitle2, 13 | sloganArrow, 14 | footer, 15 | diyName, 16 | themeColors, 17 | imgData 18 | } from './config'; 19 | 20 | import Loading from './loading'; 21 | import Selector from './selector'; 22 | import Editor from './editor'; 23 | 24 | ((w, d) => { 25 | class Poster extends React.Component { 26 | constructor() { 27 | super(); 28 | 29 | this.state = { 30 | num : 0, 31 | position : '', 32 | photo : '', 33 | sloganOne : '', 34 | sloganTwo : '', 35 | sloganThree: '', 36 | allowGen : false 37 | }; 38 | 39 | this.onNumChange = this.onNumChange.bind(this); 40 | this.onPositionChange = this.onPositionChange.bind(this); 41 | this.handleSloganOneChange = this.handleSloganOneChange.bind(this); 42 | this.handleSloganTwoChange = this.handleSloganTwoChange.bind(this); 43 | this.handleSloganThreeChange = this.handleSloganThreeChange.bind(this); 44 | this.handlePhotoLoad = this.handlePhotoLoad.bind(this); 45 | this.donePosterPreview = this.donePosterPreview.bind(this); 46 | } 47 | 48 | componentDidMount() { 49 | setTimeout(() => { 50 | $('.poster').fullpage({ 51 | touchSensitivity: 15, 52 | normalScrollElementTouchThreshold: 5 53 | }); 54 | 55 | w.disableTouchMove = false; 56 | $.fn.fullpage.moveTo(2); 57 | }, 4000); 58 | } 59 | 60 | 61 | onNumChange(n) { 62 | this.setState({ num: n }); 63 | } 64 | 65 | onPositionChange(pos) { 66 | this.setState({ position: pos }); 67 | } 68 | 69 | handleSloganOneChange(content) { 70 | this.setState({ sloganOne: content }); 71 | } 72 | 73 | handleSloganTwoChange(content) { 74 | this.setState({ sloganTwo: content }); 75 | } 76 | 77 | handleSloganThreeChange(content) { 78 | this.setState({ sloganThree: content }); 79 | } 80 | 81 | handlePhotoLoad(event) { 82 | let source = event.target.files[0], 83 | reader = new FileReader(); 84 | 85 | reader.onload = (event) => { 86 | this.setState({ photo: event.target.result }); 87 | }; 88 | 89 | reader.readAsDataURL(source); 90 | } 91 | 92 | donePosterPreview() { 93 | this.setState({ allowGen: true }); 94 | } 95 | 96 | render() { 97 | return ( 98 |
99 | 100 | 116 | 0 ? imgData[this.state.num - 1].src : this.state.photo } 124 | themeColors={ themeColors } 125 | posterSize={ posterSize } 126 | titleOne={ photoTitle1 } 127 | titleTwo={ photoTitle2 } 128 | sloganArrow={ sloganArrow } 129 | footer={ footer } 130 | allowGen={ this.state.allowGen } 131 | donePosterPreview={ this.donePosterPreview } 132 | handleSloganOneChange={ this.handleSloganOneChange } 133 | handleSloganTwoChange={ this.handleSloganTwoChange } 134 | handleSloganThreeChange={ this.handleSloganThreeChange } 135 | /> 136 |
137 | ); 138 | } 139 | }; 140 | 141 | try { 142 | ReactDOM.render(, $('#poster')[0]); 143 | 144 | w.disableTouchMove = true; 145 | 146 | $(w).on('touchmove', (event) => { 147 | if (disableTouchMove) { 148 | event.preventDefault(); 149 | } 150 | }); 151 | } catch(error) { 152 | $('.result').fullpage({ 153 | touchSensitivity: 15, 154 | normalScrollElementTouchThreshold: 5 155 | }); 156 | 157 | $('#download').click(() => { 158 | $('.download-info').addClass('active'); 159 | }); 160 | 161 | $('#new').click(() => { 162 | w.location = '/poster'; 163 | }); 164 | 165 | $('#share').click(() => { 166 | $('.share-info').addClass('active'); 167 | }); 168 | 169 | $('.close').click(() => { 170 | $('.download-info').removeClass('active'); 171 | $('.share-info').removeClass('active'); 172 | }); 173 | } 174 | })(window, document); -------------------------------------------------------------------------------- /src/scss/components/_selector.scss: -------------------------------------------------------------------------------- 1 | // _selector.scss 2 | 3 | @import '../../node_modules/swiper/dist/css/swiper.css'; 4 | 5 | $prevWidth: 69vw; 6 | $prevHeight: 1.6 * $prevWidth; 7 | 8 | .selector { 9 | display: block; 10 | position: relative; 11 | width: 100%; 12 | height: 100vh; 13 | padding-top: 5.5vh; 14 | 15 | .hide { 16 | display: none !important; 17 | } 18 | 19 | .bg { 20 | background-color: $lgray; 21 | } 22 | 23 | .title { 24 | display: block; 25 | position: relative; 26 | width: 100%; 27 | text-align: center; 28 | font-size: 0.875rem; 29 | color: $dgray; 30 | margin-bottom: 3.75vh; 31 | 32 | .pos-show, 33 | .pos-edit { 34 | display: inline-block; 35 | position: relative; 36 | font: { 37 | size: 1.375rem; 38 | weight: 400; 39 | } 40 | color: $black; 41 | line-height: 1em; 42 | margin-left: 0.25em; 43 | } 44 | 45 | .pos-edit { 46 | padding: 0; 47 | border: none; 48 | } 49 | 50 | .pos-status { 51 | display: inline-block; 52 | position: absolute; 53 | top: 0.625rem; 54 | right: 6vw; 55 | height: 1em; 56 | font-size: 0.75rem; 57 | line-height: 1em; 58 | 59 | &.edit { 60 | svg { 61 | width: 0.75rem; 62 | height: 0.75rem; 63 | margin-right: 0.25em; 64 | } 65 | } 66 | } 67 | } 68 | 69 | .demos { 70 | display: block; 71 | position: relative; 72 | width: 100%; 73 | height: 100%; 74 | padding-top: 5px; 75 | 76 | .swiper-wrapper { 77 | position: relative; 78 | width: 100%; 79 | 80 | .swiper-slide { 81 | display: block; 82 | position: relative; 83 | width: $prevWidth; 84 | height: $prevHeight; 85 | padding: 4.666666667vw 3vw; 86 | box-shadow: 2px 2px 10px $dgray; 87 | 88 | &:nth-child(4n+1) { 89 | background-color: $sblue; 90 | } 91 | 92 | &:nth-child(4n+2) { 93 | background-color: $sgreen; 94 | } 95 | 96 | &:nth-child(4n+3) { 97 | background-color: $spink; 98 | } 99 | 100 | &:nth-child(4n) { 101 | background-color: $syellow; 102 | } 103 | 104 | .title-1 { 105 | display: block; 106 | position: relative; 107 | width: 42.666666667vw; 108 | margin-bottom: 1.6vw; 109 | } 110 | 111 | .title-2 { 112 | display: block; 113 | position: relative; 114 | width: 21.333333333vw; 115 | margin-bottom: 4vw; 116 | } 117 | 118 | > ul { 119 | display: block; 120 | position: relative; 121 | width: 100%; 122 | margin: 0; 123 | margin-bottom: 10vw; 124 | padding: 0; 125 | list-style: none; 126 | 127 | li { 128 | display: block; 129 | position: relative; 130 | width: 100%; 131 | font: { 132 | size: 2.133333333vw; 133 | weight: 300; 134 | } 135 | margin-bottom: 2.4vw; 136 | line-height: 1em; 137 | 138 | img { 139 | display: inline-block; 140 | position: relative; 141 | width: 1.333333333vw; 142 | margin-right: 0.25em; 143 | } 144 | } 145 | } 146 | 147 | .character { 148 | display: block; 149 | position: relative; 150 | width: 100%; 151 | } 152 | 153 | .character-diy { 154 | display: block; 155 | position: absolute; 156 | left: 0; 157 | width: 100%; 158 | height: 33vh; 159 | opacity: 0.15; 160 | z-index: 5; 161 | } 162 | 163 | .diy-photo { 164 | width: 0.1px; 165 | height: 0.1px; 166 | opacity: 0; 167 | overflow: hidden; 168 | position: absolute; 169 | z-index: -1; 170 | } 171 | 172 | .diy-photo + label { 173 | display: block; 174 | position: absolute; 175 | width: 18vw; 176 | height: 18vw; 177 | left: 25.5vw; 178 | margin-top: ($prevHeight - 18vw - 4.666666667vw*2)/4; 179 | border: 1px dashed $dgray; 180 | border-radius: 50%; 181 | cursor: pointer; 182 | padding: 5vw; 183 | z-index: 10; 184 | 185 | i:first-child { 186 | display: block; 187 | position: absolute; 188 | top: 9vw; 189 | width: 8vw; 190 | height: 1px; 191 | background-color: $dgray; 192 | } 193 | 194 | i:last-child { 195 | display: block; 196 | position: absolute; 197 | left: 9vw; 198 | width: 1px; 199 | height: 8vw; 200 | background-color: $dgray; 201 | } 202 | } 203 | } 204 | } 205 | } 206 | 207 | .next-edit { 208 | display: block; 209 | position: absolute; 210 | width: 100%; 211 | bottom: 0; 212 | 213 | span { 214 | display: block; 215 | position: relative; 216 | width: 100%; 217 | text-align: center; 218 | font-size: 3.2vw; 219 | line-height: 1em; 220 | margin-bottom: 4vw; 221 | color: $dgray; 222 | } 223 | 224 | .next-slide { 225 | display: block; 226 | position: relative; 227 | width: 4.67vw; 228 | height: 4.67vw; 229 | margin: 0 auto 3vh; 230 | animation: slideUp 1s infinite; 231 | 232 | svg { 233 | display: block; 234 | fill: $dgray; 235 | width: 4.67vw; 236 | height: 2.67vw; 237 | margin-bottom: -0.5vh; 238 | } 239 | } 240 | } 241 | } -------------------------------------------------------------------------------- /src/scss/components/_result.scss: -------------------------------------------------------------------------------- 1 | // _result.scss 2 | 3 | .result { 4 | display: block; 5 | position: relative; 6 | width: 100%; 7 | 8 | .show { 9 | display: block; 10 | position: relative; 11 | width: 100vw; 12 | height: 100vh; 13 | padding: { 14 | top: 5vh; 15 | } 16 | 17 | .photo { 18 | display: block; 19 | position: relative; 20 | height: 72.5vh; 21 | margin: 0 auto 4.25vh; 22 | box-shadow: 2px 2px 10px $dgray; 23 | } 24 | 25 | .choose { 26 | display: block; 27 | position: relative; 28 | width: 80vw; 29 | margin: 0 auto; 30 | text-align: center; 31 | 32 | > a { 33 | display: inline-block; 34 | position: relative; 35 | border: 1px solid $border-gray { 36 | radius: 10px; 37 | } 38 | text-align: center; 39 | height: 8vw; 40 | width: 16vw; 41 | line-height: 8vw; 42 | color: $dgray; 43 | background-color: $white; 44 | font: { 45 | size: 3.6vw; 46 | weight: 400; 47 | } 48 | cursor: pointer; 49 | text-decoration: none; 50 | 51 | &#new { 52 | width: 39.2vw; 53 | background-color: $blue; 54 | color: $white; 55 | margin: 0 4.4vw; 56 | } 57 | } 58 | } 59 | 60 | .next { 61 | display: block; 62 | position: absolute; 63 | width: 100%; 64 | bottom: 0; 65 | 66 | span { 67 | display: block; 68 | position: relative; 69 | width: 100%; 70 | text-align: center; 71 | font-size: 3.2vw; 72 | line-height: 1em; 73 | margin-bottom: 2vw; 74 | color: $dgray; 75 | } 76 | 77 | .next-slide { 78 | display: block; 79 | position: relative; 80 | width: 4.67vw; 81 | height: 4.67vw; 82 | margin: 0 auto 2vh; 83 | animation: slideUp 1s infinite; 84 | 85 | svg { 86 | display: block; 87 | fill: $dgray; 88 | width: 4.67vw; 89 | height: 2.67vw; 90 | margin-bottom: -0.5vh; 91 | } 92 | } 93 | } 94 | } 95 | 96 | .more { 97 | display: block; 98 | position: relative; 99 | width: 100vw; 100 | height: 100vh; 101 | padding: 7vh 6.4vw; 102 | 103 | .next { 104 | display: block; 105 | position: absolute; 106 | width: 100%; 107 | bottom: 0; 108 | left: 0; 109 | 110 | span { 111 | display: block; 112 | position: relative; 113 | width: 100%; 114 | text-align: center; 115 | font-size: 3.2vw; 116 | line-height: 1em; 117 | margin-bottom: 2vw; 118 | color: $dgray; 119 | } 120 | 121 | .next-slide { 122 | display: block; 123 | position: relative; 124 | width: 4.67vw; 125 | height: 4.67vw; 126 | margin: 0 auto 2vh; 127 | animation: slideUp 1s infinite; 128 | 129 | svg { 130 | display: block; 131 | fill: $dgray; 132 | width: 4.67vw; 133 | height: 2.67vw; 134 | margin-bottom: -0.5vh; 135 | } 136 | } 137 | } 138 | 139 | .to { 140 | display: block; 141 | position: relative; 142 | width: 100%; 143 | line-height: 1em; 144 | font: { 145 | size: 4.4vw; 146 | weight: 400; 147 | } 148 | color: $dgray; 149 | margin-bottom: 5vh; 150 | } 151 | 152 | .sentences { 153 | display: block; 154 | position: relative; 155 | width: 100%; 156 | margin: 0; 157 | padding: 0; 158 | list-style: none; 159 | 160 | li { 161 | display: block; 162 | position: relative; 163 | width: 100%; 164 | line-height: 1em; 165 | font: { 166 | size: 4.4vw; 167 | weight: 400; 168 | } 169 | color: $black; 170 | margin-bottom: 2.5vh; 171 | text-align: center; 172 | 173 | &:last-child { 174 | margin-bottom: 5.5vh; 175 | } 176 | } 177 | } 178 | 179 | .from { 180 | display: block; 181 | position: relative; 182 | width: 100%; 183 | line-height: 1em; 184 | font: { 185 | size: 4.4vw; 186 | weight: 400; 187 | } 188 | color: $dgray; 189 | margin-bottom: 11.5vh; 190 | text-align: right; 191 | } 192 | 193 | .qr { 194 | display: block; 195 | position: relative; 196 | width: 52.4vw; 197 | margin: 0 auto; 198 | } 199 | } 200 | } 201 | 202 | .download-info, 203 | .share-info { 204 | display: none; 205 | position: absolute; 206 | width: 100vw; 207 | height: 100vh; 208 | top: 0; 209 | left: 0; 210 | background-color: rgba(0, 0, 0, 0.7); 211 | padding-top: 42.5vh; 212 | z-index: 10; 213 | 214 | .close { 215 | display: block; 216 | position: absolute; 217 | width: 5.6vw; 218 | height: 5.6vw; 219 | top: 13.5vh; 220 | right: 4vw; 221 | cursor: pointer; 222 | transform: rotate(45deg); 223 | 224 | &:before { 225 | content: ''; 226 | display: block; 227 | position: absolute; 228 | top: 2.8vw; 229 | width: 5.6vw; 230 | height: 2px; 231 | background-color: $white; 232 | } 233 | 234 | &:after { 235 | content: ''; 236 | display: block; 237 | position: absolute; 238 | left: 2.8vw; 239 | width: 2px; 240 | height: 5.6vw; 241 | background-color: $white; 242 | } 243 | } 244 | 245 | svg { 246 | display: block; 247 | position: relative; 248 | fill: $white; 249 | width: 10.4vw; 250 | height: 10.4vw; 251 | margin: 0 auto 3vh; 252 | } 253 | 254 | .info { 255 | display: block; 256 | position: relative; 257 | width: 100%; 258 | text-align: center; 259 | color: $white; 260 | font: { 261 | size: 3.6vw; 262 | weight: 400; 263 | } 264 | } 265 | 266 | &.active { 267 | display: block; 268 | } 269 | } -------------------------------------------------------------------------------- /src/js/selector.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import $ from 'jquery'; 3 | import Swiper from 'swiper'; 4 | 5 | export default class extends React.Component { 6 | constructor() { 7 | super(); 8 | 9 | this.handlePositionChange = this.handlePositionChange.bind(this); 10 | this.editPosition = this.editPosition.bind(this); 11 | this.quitEditPosition = this.quitEditPosition.bind(this); 12 | } 13 | 14 | componentDidMount() { 15 | this.props.handleNumChange(1); 16 | this.props.handlePositionChange(this.props.data[1].name); 17 | 18 | let that = this, swiper = new Swiper('.swiper-container', { 19 | effect: 'coverflow', 20 | centeredSlides: true, 21 | slidesPerView: 'auto', 22 | initialSlide : 1, 23 | longSwipes: false, 24 | followFinger: false, 25 | CSSWidthAndHeight: true, 26 | coverflow: { 27 | rotate: 50, 28 | stretch: 0, 29 | depth: 100, 30 | modifier: 1, 31 | slideShadows: false 32 | }, 33 | 34 | onSlideNextStart: (swiper) => { 35 | let n = that.props.data[that.props.posNum].name, 36 | one = that.props.data[that.props.posNum].slogan1, 37 | two = that.props.data[that.props.posNum].slogan2, 38 | three = that.props.data[that.props.posNum].slogan3; 39 | 40 | that.props.handleNumChange(that.props.posNum + 1); 41 | that.props.handlePositionChange(n); 42 | that.props.handleSloganOneChange(one); 43 | that.props.handleSloganTwoChange(two); 44 | that.props.handleSloganThreeChange(three); 45 | }, 46 | onSlidePrevStart: (swiper) => { 47 | let n = that.props.posNum > 1 ? that.props.data[that.props.posNum - 2].name : that.props.dName, 48 | one = that.props.posNum > 1 ? that.props.data[that.props.posNum - 2].slogan1 : '', 49 | two = that.props.posNum > 1 ? that.props.data[that.props.posNum - 2].slogan2 : '', 50 | three = that.props.posNum > 1 ? that.props.data[that.props.posNum - 2].slogan3 : ''; 51 | 52 | that.props.handleNumChange(that.props.posNum - 1); 53 | that.props.handlePositionChange(n); 54 | that.props.handleSloganOneChange(one); 55 | that.props.handleSloganTwoChange(two); 56 | that.props.handleSloganThreeChange(three); 57 | } 58 | }); 59 | } 60 | 61 | handlePositionChange(event) { 62 | this.props.handlePositionChange(event.target.value); 63 | } 64 | 65 | editPosition(event) { 66 | event.stopPropagation(); 67 | 68 | $('#pos_show').addClass('hide'); 69 | $('#pos_status_edit').addClass('hide'); 70 | $('#pos_edit').removeClass('hide').focus(); 71 | $('#pos_status_res').removeClass('hide'); 72 | } 73 | 74 | quitEditPosition(event) { 75 | $('#pos_show').removeClass('hide'); 76 | $('#pos_status_edit').removeClass('hide'); 77 | $('#pos_edit').addClass('hide'); 78 | $('#pos_status_res').addClass('hide'); 79 | } 80 | 81 | render() { 82 | let that = this, 83 | demos = this.props.data.map((d, i) => { 84 | return ( 85 |
89 | 90 | 91 | 105 | 106 |
107 | ); 108 | }); 109 | 110 | return ( 111 |
115 |
116 | 我是 117 | 122 | { this.props.posName } 123 | 124 | {event.stopPropagation();} } 132 | size={ this.props.posName.length + 1 } 133 | minLength='1' 134 | maxLength={ this.props.posLimit } 135 | /> 136 |
141 | 142 | 编辑 143 |
144 |
148 | 剩余{ this.props.posLimit - this.props.posName.length }字 149 |
150 |
151 |
152 |
153 |
157 | 158 | 159 |
    160 |
  • 161 | 162 | 本页可以编辑职位头衔 163 |
  • 164 |
  • 165 | 166 | 套路口号在下一页修改 167 |
  • 168 |
  • 169 | 170 | 先预览才能生成发布 171 |
  • 172 |
173 | 177 | 184 | 188 |
189 | { demos } 190 |
191 |
192 |
193 | 编辑文案 194 |
195 | 196 | 197 |
198 |
199 |
200 | ); 201 | } 202 | } -------------------------------------------------------------------------------- /src/js/editor.js: -------------------------------------------------------------------------------- 1 | import $ from 'jquery'; 2 | import React from 'react'; 3 | 4 | export default class extends React.Component { 5 | constructor() { 6 | super(); 7 | 8 | this.state = { 9 | sloganOne : '', 10 | sloganTwo : '', 11 | sloganThree: '', 12 | color : 'white' 13 | }; 14 | 15 | this.handleSloganOneChange = this.handleSloganOneChange.bind(this); 16 | this.handleSloganTwoChange = this.handleSloganTwoChange.bind(this); 17 | this.handleSloganThreeChange = this.handleSloganThreeChange.bind(this); 18 | this.handleColorSelect = this.handleColorSelect.bind(this); 19 | this.genPoster = this.genPoster.bind(this); 20 | this.onPosterPreview = this.onPosterPreview.bind(this); 21 | this.onPosterGenerate = this.onPosterGenerate.bind(this); 22 | this.hideCanvas = this.hideCanvas.bind(this); 23 | } 24 | 25 | componentWillReceiveProps(nextProps) { 26 | this.setState({ 27 | sloganOne : nextProps.sloganOne, 28 | sloganTwo : nextProps.sloganTwo, 29 | sloganThree: nextProps.sloganThree 30 | }); 31 | } 32 | 33 | handleSloganOneChange(event) { 34 | this.props.handleSloganOneChange(event.target.value); 35 | } 36 | 37 | handleSloganTwoChange(event) { 38 | this.props.handleSloganTwoChange(event.target.value); 39 | } 40 | 41 | handleSloganThreeChange(event) { 42 | this.props.handleSloganThreeChange(event.target.value); 43 | } 44 | 45 | handleColorSelect(event) { 46 | $(event.target).addClass('active').siblings().map((i, arr) => { $(arr).removeClass('active'); }); 47 | this.setState({ color: event.target.dataset.color }); 48 | } 49 | 50 | genPoster() { 51 | let that = this, 52 | canvas = $('#poster_canvas')[0], 53 | context = canvas.getContext('2d'), 54 | title1 = new Image(), 55 | title2 = new Image(), 56 | arrow = new Image(), 57 | photo = new Image(), 58 | footer = new Image(); 59 | 60 | title1.onload = () => { 61 | context.drawImage(title1, 29.8, 53.3); 62 | }; 63 | 64 | title2.onload = () => { 65 | context.drawImage(title2, 29.8, 126.6); 66 | }; 67 | 68 | arrow.onload = () => { 69 | context.drawImage(arrow, 29.8, 231.1); 70 | context.drawImage(arrow, 29.8, 290.2); 71 | context.drawImage(arrow, 29.8, 345.8); 72 | }; 73 | 74 | photo.onload = () => { 75 | context.drawImage(photo, 0, 492, 750, 659); 76 | }; 77 | 78 | footer.onload = () => { 79 | context.drawImage(footer, 0, that.props.posterSize.mainHe); 80 | }; 81 | 82 | context.fillStyle = that.props.themeColors[that.state.color]; 83 | context.fillRect(0, 0, that.props.posterSize.width, that.props.posterSize.mainHe); 84 | 85 | context.fillStyle = '#fff'; 86 | context.fillRect(0, that.props.posterSize.mainHe, that.props.posterSize.width, that.props.posterSize.height); 87 | 88 | title1.src = that.props.titleOne; 89 | title2.src = that.props.titleTwo; 90 | arrow.src = that.props.sloganArrow; 91 | 92 | context.font = '28px Source Han Sans, Helvetica Neue, Microsoft Yahei'; 93 | context.fillStyle = '#000'; 94 | 95 | context.fillText(that.state.sloganOne, 57.2, 255); 96 | context.fillText(that.state.sloganTwo, 57.2, 310.8); 97 | context.fillText(that.state.sloganThree, 57.2, 366.3); 98 | 99 | context.font = '24px Source Han Sans, Helvetica Neue, Microsoft Yahei'; 100 | context.fillStyle = '#727171'; 101 | 102 | context.fillText('—— From ' + that.props.posName, 29.8, 437.7); 103 | 104 | photo.src = that.props.photo; 105 | 106 | footer.src = that.props.footer; 107 | } 108 | 109 | onPosterPreview(event) { 110 | event.stopPropagation(); 111 | 112 | $('#poster_canvas').addClass('active'); 113 | $('.poster-bg').addClass('active'); 114 | 115 | this.genPoster(); 116 | this.props.donePosterPreview(); 117 | } 118 | 119 | onPosterGenerate(event) { 120 | event.stopPropagation(); 121 | 122 | let img = $('#poster_canvas')[0].toDataURL(), 123 | data = { 124 | position: this.props.posName, 125 | slogan : this.state.sloganOne 126 | }; 127 | 128 | $.ajax({ 129 | type: 'POST', 130 | url : '/poster/upload', 131 | data: { 132 | pic : img, 133 | data: JSON.stringify(data) 134 | } 135 | }).done((result) => { 136 | window.location = '/poster/detail/' + (JSON.parse(result)).key; 137 | }); 138 | } 139 | 140 | hideCanvas() { 141 | $('#poster_canvas').removeClass('active'); 142 | $('.poster-bg').removeClass('active'); 143 | } 144 | 145 | render() { 146 | return ( 147 |
151 |
用毕生代码修炼的三行套路
152 |
每行可输入 { this.props.sloganLimit } 字
153 |
    154 |
  • 155 | 162 |
  • 163 |
  • 164 | 171 |
  • 172 |
  • 173 | 180 |
  • 181 |
182 |
选择背景色
183 |
    184 |
  • 189 |
  • 194 |
  • 199 |
  • 204 |
  • 209 |
210 |
    211 |
  • 216 | 生成 217 |
  • 218 |
  • 222 | 预览 223 |
  • 224 |
225 | 231 |
232 |
233 | ); 234 | } 235 | } -------------------------------------------------------------------------------- /dist/poster/icons.svg: -------------------------------------------------------------------------------- 1 | 2 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | --------------------------------------------------------------------------------