How To Add React Powered CSS Filters To Twilio Video

May 11, 2016
Written by

css-filters

What’s that? You want to add filters to your video application that serve no purpose other than looking awesome? Check out how easy adding CSS filters are to elements on your page.

In a recent post we saw how to introduce React.js into an existing Twilio Video application. Let’s pick up where we left off and add our video filters as a new React component.

Start by cloning this repo and moving into it:

git clone -b conversation-react-component git@github.com:eddiezane/react-twilio-video-post.git
cd react-twilio-video-post

Follow the instructions in the README.md to get your credentials configured. If you want to learn how this app was built go back and run through this post. It will take you about 10 minutes.

Fire up the app:

node .

Open http://localhost:3000 in two browser tabs to confirm everything is working (it’s secretly required that you make a goofy face).

Filters Are Simple

CSS filters are really straightforward to use. They are simply CSS rules that we’ll apply to the video div’s via a CSS class. We’ll base our filters off of this excellent example.

Start by opening up our application’s stylesheet located at public/style.css and adding in the following classes to the bottom:

.nofilter {}

.sepia {
  -webkit-filter: sepia(1);
  filter: sepia(1);
}

.invert {
  -webkit-filter: invert(0.8);
  filter: invert(0.8);
}

.grayscale {
  -webkit-filter: grayscale(1);
  filter: grayscale(1);
}

.saturate {
  -webkit-filter: saturate(4);
  filter: saturate(4);
}

.blur {
  -webkit-filter: blur(3px);
  filter: blur(3px);
}

.filter-list {
  list-style: none;
}

This set of filters will change the video to sepia, inverted, grayscale, over saturated, and blurry. You should get creative and add your own to the list as well. The example shows how you can combine filters.

Next we’ll introduce two new React components to handle applying the filters.

The Filter component will represent a single filter as an li whose name value is passed in via a prop along with a filterClickCallback to handle the filter being selected. The FilterList component will create and hold all of the Filter‘s inside a ul.

Open up our React code at public/conversationcontainer.jsx and add the FilterList component at the bottom:

 

class FilterList extends React.Component {
  render() {
    const filters = this.props.filters.map((filter, index) => {
      return <Filter name={filter} key={index} filterClickCallback={this.props.filterClickCallback} />;
    });

    return (
      <ul className='filter-list'>
        {filters}
      </ul>
    );
  }
}

The render method maps over the list of filter properties that are passed in and creates a new Filter for each.

Now add the Filter component at the bottom.

class Filter extends React.Component {
  render() {
    const name = this.props.name;
    return (
      <li>
        <button onClick={() => this.props.filterClickCallback(name)}>
          {name}
        </button>
      </li>
    );
  }
}

Add a constructor to the existing ConversationContainer to establish the initial filter list. We’re also binding the filterClickCallback method which we’ll write next.

module.exports = class ConversationContainer extends React.Component {
  constructor(props) {
    super(props);
    const filters = ['none', 'sepia', 'invert', 'grayscale', 'saturate', 'blur'];
    this.state = { filters };
    this.filterClickCallback = this.filterClickCallback.bind(this);
  }

Right below the constructor add the filterClickCallback method which will update the state of our active filter:

  filterClickCallback(filter) {
    this.setState({
      activeFilter: filter
    });
  }

Then modify the render method of ConversationContainer to include the FilterList and filter CSS classes from our state:

render() {
    return (
      <div>
        <div ref='remoteMedia' className={`media-container ${this.state.activeRemoteFilter}`}></div>
        <div ref='localMedia' className={`media-container ${this.state.activeFilter}`}></div>
        <FilterList filters={this.state.filters} filterClickCallback={this.filterClickCallback} />
      </div>
    );
  }

Start the server back up and open http://localhost:3000 in two browser windows again. Connect a conversation and start clicking to apply the video filters. You should seriously consider making a scary monster face here.

Getting Real-Time

You may have noticed that the filter isn’t applied to both pages. We need some way of communicating that the other client should show whichever filter we have selected for our video on their page.

I’ve chose to use one of my favorite libraries, Socket.io, to solve this. Socket.io is a WebSocket library with an easy to use API. Check out my buddy Sam’s post if you haven’t used Socket.io before.

Install Socket.io by running:

npm install —save socket.io

Now let’s configure our server to use Socket.io. Open index.js and make the following changes:

// Create http server and run it
var server = http.createServer(app);
var io = require('socket.io')(server);
var port = process.env.PORT || 3000;
server.listen(port, function() {
    console.log('Express server running on *:' + port);
});

io.on('connection', function(socket) {
  socket.on('filter', function(data) {
    socket.broadcast.emit('filter', data);
  });
});

We’ve imported the library,connected it to our Express server and added a listener for a custom filter event that our clients will emit when a new filter is selected. Time to wire the client up.

Pop back into public/conversationcontainer.jsx and import the client side library:

const React = require('react');
const ReactDOM = require('react-dom');
const io = require('socket.io-client');

Inside componentDidMount initialize the Socket.io client and listen for the filter event to update the state:

componentDidMount() {
    const conversation = this.props.conversation;
    conversation.localMedia.attach(this.refs.localMedia);

    conversation.on('participantConnected', participant => {
      participant.media.attach(this.refs.remoteMedia);
    });

    const socket = io('/');

    socket.on('filter', filter => {
      this.setState({
        activeRemoteFilter: filter
      });
    });

    this.setState({ socket });
  }

Finally, emit the same event at the top of filterClickCallback:

filterClickCallback(filter) {
    this.state.socket.emit('filter', filter);

Start up the app one last time and open your browser tabs. Connect one to the other and start clicking your new filter buttons and watch as they are applied across browsers.

Wrapping It Up

CSS filters are a quick and easy way to add a bit of fun and customization to your web apps. The best part is that these CSS filters can be applied to everything from images to iFrames, or as we saw in this post, Video!

Using React we were able to create a set of reusable components to encapsulate our filter UI into a set of declarative elements which cut down on the complexity and length of our UI code. Those components would be at home in any UI, from the most complex to what we used them for: adding a bit of fun and pizazz.

Finally, throw in few lines ofSocket.io to coordinate filters across clients and you’ve built yourself a simple way to share the fun with your video conference friends.

Have a crack at adding some filters of your own and be sure to let me know what you come up with.