Creating a Cloudinary Vue Component

By  on  
Vue JS

While React.js has taken much of the attention during the latest wave of JavaScript frameworks, Vue.js has quietly become a favorite of many developers that find React overly complex and don't want to deal with the webpack tooling.  With Vue you can simply include the Vue JavaScript file in the page, create a few templates, and you're on your way -- a throwback of the original JavaScript framework days.

Cloudinary, the awesome media storage and delivery service, provides APIs in just about every language to aid developers in using their service, including Node.js, Python, PHP, React, etc.  I've been meaning to check out Vue and thought what better way to do so than creating media-centric components with the help of Cloudinary's API.  My goal was to create a video component that mirrors what you see on many video-centric sites:  load a thumbnail, play preview upon hover, and finally play the video when clicked.  Let's go!

Quick note: the Vue.js component I'm creating for this post could be further optimized (use a single <video> element, swap out controls, animated transitions, etc.) but I want to keep this post as simple and focused as possible.  The main goal is to illustrate how Cloudinary and Vue.js are complimentary to each other and they're both incredibly easy to use!

Vue Component

I wanted to create a component because, much like React, they're easily contained and reusable.  Let's start by taking a look at the component template.

Component Template

Seeing the HTML skeleton will provide insight into what we'll be manipulating:

<div v-on:mouseenter="showPreview()" v-on:mouseleave="hidePreview()" class="cloudinary-video-item" :style="dimensions">
  <div class="cloudinary-video-item-image">
    <img :src="poster" :width="width" :height="height" alt="Video Preview">
  </div>
  <div class="cloudinary-video-item-active">
    <video ref="previewVideo" autoplay loop :width="width" :height="height"></video>
  </div>
  <div class="cloudinary-video-item-video">
    <video ref="fullVideo" autoplay controls :width="width" :height="height"></video>
  </div>
  <svg
     v-on:click="play()"
     xmlns:dc="http://purl.org/dc/elements/1.1/"
     xmlns:cc="http://creativecommons.org/ns#"
     xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
     xmlns:svg="http://www.w3.org/2000/svg"
     xmlns="http://www.w3.org/2000/svg"
     id="play-icon"
     version="1.1"
     height="50"
     width="50"
     viewBox="0 0 1200 1200">
    <path
       d="M 600,1200 C 268.65,1200 0,931.35 0,600 0,268.65 268.65,0 600,0 c 331.35,0 600,268.65 600,600 0,331.35 -268.65,600 -600,600 z M 450,300.45 450,899.55 900,600 450,300.45 z"
       id="path16995" />
  </svg>
</div>

Our component has four immediate child elements:  three elements which show or display based on a CSS state, and a SVG "play" icon.  The states are:

  1. (default) Show a thumbnail/poster image for the video
  2. (hover) Show a stitched, aggregated video preview (much like this post)
  3. (active) Shows the entire video
These states will be manipulated by component methods changing the root element's state attribute; their visibility will be manipulated by CSS selector matching each state.

Component Properties

In the interest of keeping this component simple, I limit the number of properties to only those that are truly needed:

Vue.component('cloudinary-video', {
  props: {
    account: { type: String, required: true, default: 'david-wash-blog' },
    alias: { type: String, required: true },
    // These can be strings as they come in as attributes
    width: { type: String, default: 400 },
    height: { type: String, default: 300 }
  },

Realize that Cloudinary's transformation API is so powerful that I could add dozens of properties to harness its every power, but this post would balloon into a novel!  There are a few further properties which require computed values based on the simple properties, so let's also create those:

computed: {
  dimensions: function() {
    return `width:${this.width}px; height:${this.height}px;`;
  },
  poster: function() {
    return `http://res.cloudinary.com/${this.account}/video/upload/${this.alias}.jpg`;
  },
  preview: function() {
    return `http://res.cloudinary.com/${this.account}/video/upload/so_0,du_2/l_video:${this.alias},fl_splice,so_12/du_2/fl_layer_apply/l_video:${this.alias},fl_splice,so_24/du_2/fl_layer_apply/l_video:${this.alias},fl_splice,so_36/du_2/fl_layer_apply/l_video:${this.alias},fl_splice,so_48/du_2/fl_layer_apply/l_video:${this.alias},fl_splice,so_80/du_2/fl_layer_apply/${this.alias}.mp4`;
  },
  fullVideo: function() {
    return `http://res.cloudinary.com/${this.account}/video/upload/${this.alias}.mp4`;
  }
},

Computed properties can reference simple properties, which I employ heavily in this component.

Component Methods

Component methods will be employed to trigger functionality during our component's mouseenter, mouseleave, and click events:

methods: {
  play: function () {
    // Hide the preview
    this.hidePreview();
    // Set the state to "play" to show full video element
    this.$el.setAttribute('state', 'playing');
    // Set the full video element src
    this.$refs.fullVideo.src = this.fullVideo;
  },
  showPreview: function() {
    // If the full video is loaded and playing, ignore this event
    if(this.$el.getAttribute('state') === 'playing') {
      return;
    }
    // Update state for CSS / component's child element visibility
    this.$el.setAttribute('state', 'preview');
    // Set the preview video source
    this.$refs.previewVideo.src = this.preview;
  },
  hidePreview: function() {
    // If the full video is loaded and playing, ignore this event
    if(this.$el.getAttribute('state') === 'playing') {
      return;
    }
    // Update state for CSS / component's child element visibility
    this.$el.setAttribute('state', '');
    // Stop the video
    this.$refs.previewVideo.pause();
  }
},

While I do use the attribute state , realize that I'm not using Flux or any other state management utility -- the attribute simply represents which of the three component states should be shown or hidden.

Component CSS

The CSS required for this component seems like a lot but it mostly manages the simple layout as well as "state": showing and hiding each component child element as required:

.cloudinary-video-item {
  position: relative;
}

.cloudinary-video-item > div {
  position: absolute;
  top: 0;
  left: 0;
}

.cloudinary-video-item img {
  display: block;
}

.cloudinary-video-item svg {
  position: absolute;
  top: 40%;
  left: 45%;
  cursor: pointer;
  opacity: 0.6;
}
.cloudinary-video-item svg:hover {
  opacity: 0.9;
}

/* Default / image only state */
.cloudinary-video-item .cloudinary-video-item-active,
.cloudinary-video-item .cloudinary-video-item-video {
  display: none;
}

/* Preview state */
.cloudinary-video-item[state=preview] .cloudinary-video-item-active {
  display: block;
}
.cloudinary-video-item[state=preview] .cloudinary-video-item-image {
  display: none;
}

/* Playing state */
.cloudinary-video-item[state=playing] .cloudinary-video-item-video {
  display: block;
}
.cloudinary-video-item[state=playing] .cloudinary-video-item-image,
.cloudinary-video-item[state=playing] .cloudinary-video-item-active,
.cloudinary-video-item[state=playing] svg {
  display: none;
}

There's a fair amount there but minified would hardly leave a footprint!

Using the Component

With each prop in  props containing a default value, except for the media alias of course, the component usage can be simple:

<!-- simplest usage -->
<cloudinary-video alias="cartoon"></cloudinary-video>

<!-- customized usage -->
<cloudinary-video
  account="david-wash-blog"
  alias="cartoon"
  width="640"
  height="360">
</cloudinary-video>

And lastly adding a new Vue call to kick everything off:

new Vue({ el: '#video-holder' })

That's how easy it is to create a Vue.js component for your Cloudinary media!

Closing

Creating a Vue component that uses multiple types of generated media from a single source was easy thanks to Cloudinary.  Cloudinary generated the sample image, the video poster, the preview video, and quickly delivered those resources as well as the source video.  Vue's simple API made creating the Cloudinary component fun and short on code.  I look forward to playing around with Vue and Cloudinary to make some truly powerful media components!

Recent Features

  • By
    Convert XML to JSON with JavaScript

    If you follow me on Twitter, you know that I've been working on a super top secret mobile application using Appcelerator Titanium.  The experience has been great:  using JavaScript to create easy to write, easy to test, native mobile apps has been fun.  My...

  • By
    I&#8217;m an Impostor

    This is the hardest thing I've ever had to write, much less admit to myself.  I've written resignation letters from jobs I've loved, I've ended relationships, I've failed at a host of tasks, and let myself down in my life.  All of those feelings were very...

Incredible Demos

  • By
    Get Slick with MooTools Kwicks

    When I first saw MooTools graphical navigation, I was impressed. I thought it was a very simple yet creative way of using Flash. When I right-clicked and saw that it was JavaScript, I was floored. How could they achieve such...

  • By
    Create a Clearable TextBox with the Dojo Toolkit

    Usability is a key feature when creating user interfaces;  it's all in the details.  I was recently using my iPhone and it dawned on my how awesome the "x" icon is in its input elements.  No holding the delete key down.  No pressing it a...

Discussion

  1. Diego

    Just in case you find this useful, I’ve created a vue-cloudinary library a year ago: https://github.com/diegopamio/vue-cloudinary

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!