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
    Create a CSS Cube

    CSS cubes really showcase what CSS has become over the years, evolving from simple color and dimension directives to a language capable of creating deep, creative visuals.  Add animation and you've got something really neat.  Unfortunately each CSS cube tutorial I've read is a bit...

  • By
    5 Awesome New Mozilla Technologies You&#8217;ve Never Heard Of

    My trip to Mozilla Summit 2013 was incredible.  I've spent so much time focusing on my project that I had lost sight of all of the great work Mozillians were putting out.  MozSummit provided the perfect reminder of how brilliant my colleagues are and how much...

Incredible Demos

  • By
    PHP / MooTools 1.2 Accordion Helper

    The MooTools Accordion plugin seems to be the plugin that people seem to have the most problems with. It's an awesome plugin, so I can see why so many people want to use it, but I think that may be part of the problem.

  • By
    Styling CSS Print Page Breaks

    It's important to construct your websites in a fashion that lends well to print. I use a page-break CSS class on my websites to tell the browser to insert a page break at strategic points on the page. During the development of my...

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!