前端工程展示页 项目介绍
源代码 + 效果图
See the Pen
Challenges [07] by MaverickNone (@MaverickNone )
on CodePen .
如果客官您觉得不够大,看着不太爽! 请点击这里 或者这边 ~
源代码解构 HTML !!! 5 %html (lang ="en" ) %head %meta (charset ="UTF-8" ) %title Test [07] %script (src ="https://kit.fontawesome.com/83ef1fc2b2.js" ) %link (rel ="stylesheet" href ="style.css" ) %body #app %script (src ="https://cdnjs.cloudflare.com/ajax/libs/react/16.9.0/umd/react.production.min.js" ) %script (src ="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js" ) %script (src ="https://cdnjs.cloudflare.com/ajax/libs/marked/0.7.0/marked.min.js" ) %script (src ="https://cdn.freecodecamp.org/testable-projects-fcc/v1/bundle.js" ) %script (src ="script.js" )
本代码由Haml 编写
添加库的时候,同时把React 和React-DOM 都加上
这次渲染Markdown使用了GitHub上的Marked 项目
倒数第二个是freeCodeCamp 的项目测评库
其他没啥可讲的,都是基本功
CSS 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 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 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 @import url("https://fonts.googleapis.com/css?family=Russo+One" );@import url("https://fonts.font.im/css?family=Quantico" );$darkAccent : #09d5da ;$lightAccent : #2cda9d ;$backgroundBase : #5368a4 ;$shadow : 1px 1px 15px 8px darken($backgroundBase , 20% );$default-border : 1px solid darken($backgroundBase , 35% );body { background : url("https://i.loli.net/2019/08/14/GSTWDn2sqt3YL17.jpg" ) fixed; background-size : 100% ; }.colorScheme { background-color : lighten($backgroundBase , 30% ); box-shadow : $shadow ; border-top-style : none; }.toolbar { position : relative; background-color : lighten($darkAccent , 25% ); padding : 4px 4px 3px 3px ; border : $default-border ; box-shadow : $shadow ; font-family : Russo One, sans-serif; font-size : 15px ; i { width : 25px ; color : black; margin-left : 5px ; } .fa-compress , .fa-expand { position : absolute; right : -5px ; } }.fa-compress ,.fa-expand { &:hover { animation : btn 0.5s ; animation-fill-mode : forwards; cursor : pointer; } }@keyframes btn { 100% { color : lighten($lightAccent , 10% ); } }.fa-chess { margin-right : 3px ; }.editorWrap { width : 600px ; margin : 18px auto; .toolbar { position : relative; right : 18px ; width : 100% ; padding-right : 36px ; border-radius : 5px ; } textarea { @extend .colorScheme; width : 98.5% ; padding-left : 1vw ; min-height : 60vh ; margin-bottom : -5px ; resize : vertical; outline : none; padding-top : 5px ; font-family : Quantico, sans-serif; font-size : 15px ; border-bottom-left-radius : 20px ; border-bottom-right-radius : 20px ; &::-webkit-scrollbar { width: 0 }; } }.previewWrap { @extend .colorScheme; width : 800px ; margin : 20px auto; overflow-wrap : break-word; padding-right : 20px ; border-bottom-left-radius : 25px ; border-bottom-right-radius : 25px ; .toolbar { position : relative; right : 12px ; width : 100% ; padding-right : 36px ; border-radius : 5px ; } #preview { margin-left : 5px ; margin-top : -10px ; width : 98.5% ; border-bottom-left-radius : 25px ; border-bottom-right-radius : 25px ; padding-left : 1vw ; } }@media screen and (max-width: 850px ) { .previewWrap { width : 630px ; } .editorWrap { width : 550px ; } }.maximized { width : 90% ; min-height : 90vh ; margin : auto; textarea { min-height : 90vh ; resize : none; } }.hide { display : none; }@media screen and (max-width: 650px ) { body { margin : 5px 0 ; } .editorWrap { width : 80vw ; margin : 0 auto; } .maximized { width : 90% ; margin : auto; } .previewWrap { width : 90vw ; #preview { width : 100% ; img { height : 200px ; } } } }#preview { blockquote { border-left : 3px solid #224b4b ; color : #224b4b ; padding-left : 5px ; margin-left : 25px ; } code { background : rgba(9 , 250 , 255 , 0.31 ); padding : 1px 4px 2px 4px ; font-size : 12px ; font-weight : bold; } pre { background : white; padding : 5px 0 5px 5px ; } h1 { border-bottom : 2px solid $darkAccent ; } h2 { border-bottom : 1px solid $darkAccent ; } table { border-collapse : collapse; } td , th { border : 2px solid $darkAccent ; padding-left : 5px ; padding-right : 5px ; } }
&::-webkit-scrollbar { width: 0 };
这行代码隐藏了编辑框滚动条 ,该特性是非标准 的,请尽量不要在生产环境中使用它!
最后的Markdown预览框渲染的标签由Marked 项目自动生成,这里只是添加了一点点渲染改进
JavaScript 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 marked.setOptions({ breaks: true, smartLists: true }); const placeholder = `# Welcome to my React Markdown Previewer! ## This is a sub-heading... ### And here's some other cool stuff: Heres some code, \`<div></div>\`, between 2 backticks. \`\`\` // this is multi-line code: function anotherExample(firstLine, lastLine) { if (firstLine == '\`\`\`' && lastLine == '\`\`\`') { return multiLineCode; } } \`\`\` You can also make text **bold**... whoa! Or _italic_. Or... wait for it... **_both!_** And feel free to go crazy ~~crossing stuff out~~. There's also [links](https://www.freecodecamp.com), and > Block Quotes! And if you want to get really crazy, even tables: Wild Header | Crazy Header | Another Header? ------------ | ------------- | ------------- Your content can | be here, and it | can be here.... And here. | Okay. | I think we get it. - And of course there are lists. - Some are bulleted. - With different indentation levels. - That look like this. 1. And there are numbererd lists too. 1. Use just 1s if you want! 1. But the list goes on... - Even if you use dashes or asterisks. * And last but not least, let's not forget embedded images: ![React Logo w/ Text](https://goo.gl/Umyytc) `;
marked.setOptions()
里面是Marked 的配置代码
placeholder
里面是默认 的输入待转化字符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 const Toolbar = (props) => { return ( <div className="toolbar"> <i className="fas fa-chess" /> {props.text} <i onClick={props.onClick} className={props.icon} /> </div> ); }; const Editor = (props) => { return ( <textarea id="editor" value={props.markdown} onChange={props.onChange} /> ); }; const Preview = (props) => { return ( <div id="preview" dangerouslySetInnerHTML={{ __html: marked(props.markdown) }} /> ); };
这部分抽离 了一些React组件
dangerouslySetInnerHTML={{ __html: marked(props.markdown) }}
因为不合时宜的使用innerHTML
可能会导致**cross-site scripting (XSS)**攻击。 净化用户的输入来显示的时候,经常会出现错误,不合适的净化也是导致网页攻击的原因之一。所以,dangerouslySetInnerHTML
这个 prop 的命名是故意这么设计的,以此来警告,它的prop
值( 一个对象而不是字符串 )应该被用来表明净化后的数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 class App extends React.Component { constructor(props) { super(props); this.state = { markdown: placeholder, editorMaximized: false, previewMaximized: false }; this.handleChange = this.handleChange.bind(this); this.handleEditorMaximize = this.handleEditorMaximize.bind(this); this.handlePreviewMaximize = this.handlePreviewMaximize.bind(this); } handleChange(e) { this.setState({ markdown: e.target.value }); } handleEditorMaximize() { this.setState((prevState) => { return {editorMaximized: !prevState.editorMaximized}; }); } handlePreviewMaximize() { this.setState((prevState) => { return {previewMaximized: !prevState.previewMaximized}; }); } render() { const classes = this.state.editorMaximized ? ["editorWrap maximized", "previewWrap hide", "fas fa-compress"] : this.state.previewMaximized ? ["editorWrap hide", "previewWrap maximized", "fas fa-compress"] : ["editorWrap", "previewWrap", "fas fa-expand"]; return ( <div> <div className={classes[0]}> <Toolbar icon={classes[2]} onClick={this.handleEditorMaximize} text="MaverickNone's Editor" /> <Editor markdown={this.state.markdown} onChange={this.handleChange} /> </div> <div className={classes[1]}> <Toolbar icon={classes[2]} onClick={this.handlePreviewMaximize} text="MaverickNone's Previewer" /> <Preview markdown={this.state.markdown} /> </div> </div> ); } } ReactDOM.render(<App />, document.getElementById("app"));
这里使用了三目运算符 ,根据React保存的状态来判断是隐藏还是显示某组件(通过选择class 的方式),工具栏应该用什么图标
三目运算符后面的数组 用的非常巧妙,应该注意一下
项目总结
这次项目深化了React 的熟练度 ,以及如何配合React来动态渲染CSS (比如:如何隐藏组件)
练习了React的代码风格之一,组件抽离
这次尝试使用了Marked 项目,该项目非常好用,值得推荐和收藏
学习了如何隐藏滚动条