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

Incredible Demos

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!