Skip to content

Instantly share code, notes, and snippets.

@e1blue
Created July 12, 2018 14:52
Show Gist options
  • Select an option

  • Save e1blue/dffad254b878e59deb0108d710218fd4 to your computer and use it in GitHub Desktop.

Select an option

Save e1blue/dffad254b878e59deb0108d710218fd4 to your computer and use it in GitHub Desktop.
Interactive CSS Filter React Component (with GIFs!)
<div id="app">
</div>

Interactive CSS Filter React Component (with GIFs!)

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.

License.

//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;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment