Making a reusable React component to demonstrate CSS filters with interactive sliders. Uses a touch of CSS grid and Sass as well. Can use your own image or gif, too!
A Pen by Josh Collinsworth on CodePen.
| <div id="app"> | |
| </div> |
Making a reusable React component to demonstrate CSS filters with interactive sliders. Uses a touch of CSS grid and Sass as well. Can use your own image or gif, too!
A Pen by Josh Collinsworth on CodePen.
| //You can change this to alter the default image (or just fill out the form) | |
| const defaultImage = "https://media.giphy.com/media/BSx6mzbW1ew7K/giphy.gif"; | |
| class App extends React.Component { | |
| constructor(props){ | |
| super(props); | |
| this.state = { | |
| imgURL: defaultImage | |
| } | |
| } | |
| handleSubmit = (url) => { | |
| this.setState({ imgURL: url }) | |
| } | |
| render(){ | |
| return( | |
| <React.Fragment> | |
| <ImgInput default={defaultImage} handleSubmit={this.handleSubmit}/> | |
| <Filter type="sepia" max={1} default={0.7} src={this.state.imgURL}/> | |
| <Filter type="blur" max={12} default={2} unit="px" src={this.state.imgURL}/> | |
| <Filter type="contrast" max={500} default={50} unit="%" src={this.state.imgURL}/> | |
| <Filter type="saturate" max={10} default={2} src={this.state.imgURL}/> | |
| <Filter type="hue-rotate" max={360} default={180} unit="deg" src={this.state.imgURL}/> | |
| <Filter type="grayscale" max={100} default={100} unit="%" src={this.state.imgURL}/> | |
| <Filter type="brightness" max={5} default={1.5} src={this.state.imgURL}/> | |
| <Filter type="invert" max={1} default={1} src={this.state.imgURL}/> | |
| <Filter type="opacity" max={1} default={0.5} src={this.state.imgURL}/> | |
| </React.Fragment> | |
| ); | |
| } | |
| } | |
| //ImgInput element; handles the URL input, button, and change/submit events | |
| class ImgInput extends React.Component { | |
| constructor(props){ | |
| super(props); | |
| this.state = { | |
| imgURL: this.props.default | |
| } | |
| } | |
| handleInputChange = (e) => { | |
| this.setState({ imgURL: e.target.value }); | |
| } | |
| handleSubmit = (e) => { | |
| e.preventDefault(); | |
| this.props.handleSubmit(this.state.imgURL); | |
| } | |
| handleClick = (e) => { | |
| e.target.select(); | |
| } | |
| render(){ | |
| return( | |
| <form id="image-group" onSubmit={this.handleSubmit}> | |
| <label htmlFor="imageURL">Image URL:</label> | |
| <input type="url" id="imageURL" defaultValue={this.props.default} onClick={this.handleClick} onChange={this.handleInputChange}/> | |
| <button type="submit">Apply</button> | |
| </form> | |
| ); | |
| } | |
| } | |
| //Filter; reusable component for CSS filters | |
| class Filter extends React.Component { | |
| constructor(props){ | |
| super(props); | |
| this.state = { | |
| filterVal: this.props.default | |
| } | |
| } | |
| onFilterChange = (e) => { | |
| const img = document.querySelector(`#${this.props.type}-group img`); | |
| this.setState({ filterVal: e.target.value }); | |
| } | |
| render(){ | |
| return( | |
| <div className="filter-group" id={this.props.type + '-group'}> | |
| <div className="flex-wrapper"> | |
| <label htmlFor={this.props.type}>{this.props.type}</label> | |
| <input type="range" id={this.props.type} min={this.props.min ? this.props.min : 0 } max={this.props.max} step="0.01" onChange={this.onFilterChange} value={this.state.filterVal}/> | |
| </div> | |
| <img src={this.props.src} alt="" style={{ filter: `${this.props.type}(${this.state.filterVal}${this.props.unit ? this.props.unit : ''})`}}/> | |
| <pre>filter: {`${this.props.type}(${this.state.filterVal}${this.props.unit ? this.props.unit : ''});`}</pre> | |
| </div> | |
| ); | |
| } | |
| } | |
| ReactDOM.render(<App />, document.getElementById('app')); |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.2.0/umd/react.development.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.4.0/umd/react-dom.development.js"></script> |
| :root { | |
| --dark: #53565a; | |
| --yellow: #ffd100; | |
| --lightgray: #eee; | |
| --background: #67e4ff; | |
| --breakpoint: 420px; | |
| } | |
| * { | |
| box-sizing: border-box; | |
| } | |
| body { | |
| line-height: 1.6; | |
| margin: 2em; | |
| background: var(--background); | |
| font-size: 18px; | |
| } | |
| body, input, button { | |
| font-family: 'Nunito', sans-serif; | |
| color: var(--dark); | |
| } | |
| button { | |
| background: var(--yellow); | |
| } | |
| #app { | |
| width: 100%; | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); | |
| grid-gap: 2rem; | |
| align-content: flex-start; | |
| align-items: stretch; | |
| padding: 1rem; | |
| #image-group { | |
| width: 100%; | |
| grid-column: 1 / -1; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| @media(max-width: var(--breakpoint)){ | |
| flex-wrap: wrap; | |
| } | |
| label { | |
| line-height: 1; | |
| } | |
| input { | |
| flex: 1 1 50%; | |
| margin: 0 1rem; | |
| @media(max-width: var(--breakpoint)){ | |
| margin: .6em 0; | |
| } | |
| } | |
| button { | |
| text-transform: uppercase; | |
| } | |
| input, button { | |
| font-size: 1rem; | |
| border: 2px solid var(--dark); | |
| padding: .5em; | |
| &:focus { | |
| outline: 2px solid var(--yellow); | |
| } | |
| } | |
| } | |
| } | |
| .filter-group { | |
| margin: auto; | |
| display: flex; | |
| flex-wrap: wrap; | |
| align-items: flex-start; | |
| background: #fff; | |
| border-radius: 4px; | |
| position: relative; | |
| overflow: hidden; | |
| max-width: 100%; | |
| & > * { | |
| width: 100%; | |
| } | |
| img { | |
| height: auto; | |
| } | |
| input[type=range] { | |
| margin: 1em; | |
| } | |
| .flex-wrapper { | |
| display: flex; | |
| justify-content: space-between; | |
| padding: 1rem; | |
| } | |
| label { | |
| text-transform: capitalize; | |
| font-size: 1.4rem; | |
| display: flex; | |
| align-items: center; | |
| } | |
| pre { | |
| background: var(--lightgray); | |
| color: var(--dark); | |
| padding: 1.2rem; | |
| font-size: 14px; | |
| font-weight: bold; | |
| background-color: var(--lightgray); | |
| margin: 0; | |
| } | |
| } |