Vue.js Element Query Component

Using scoped slots and render functions to create a UI-less component that provides child components with information about their size.

by Adam Wathan

HTML

<link rel="stylesheet" href="https://unpkg.com/tailwindcss@0.4.0/dist/tailwind.min.css">
<script src="https://unpkg.com/vue@2.5.13/dist/vue.js"></script>
<script src="https://unpkg.com/element-resize-detector@1.1.12/dist/element-resize-detector.min.js"></script>
<div id="app" class="p-8">
  <div class="mx-auto mb-8" style="width: 512px;">
    <profile-card></profile-card>
  </div>
  <div class="mx-auto" style="width: 320px;">
    <profile-card></profile-card>
  </div>
</div>

Babel + JSX

const erd = elementResizeDetectorMaker({ strategy: "scroll" })

Vue.component('element-query', {
  data() {
    return {
      width: null,
      height: null,
    }
  },
  mounted() {
    erd.listenTo(this.$el, (el) => {
      this.width = el.offsetWidth;
      this.height = el.offsetHeight;
    })
  },
  render() {
    return this.$scopedSlots.default({
      width: this.width,
      height: this.height,
    })
  },
})

Vue.component('profile-card', {
  template: `
    <element-query>
      <div slot-scope="{ width }" class="bg-white shadow-lg rounded-lg overflow-hidden">
        <div :class="['px-6 py-4', { 'flex items-center': width >= 512 }]">
          <img src="https://avatars2.githubusercontent.com/u/4323180?s=400&u=4962a4441fae9fba5f0f86456c6c506a21ffca4f&v=4" alt=""
            :class="['block h-16 rounded-full mx-auto mb-4', { 'h-24 mb-0 mr-4 ml-0': width >= 512 }]"
          >
          <div :class="['text-center', { 'sm:text-left sm:flex-grow': width >= 512 }]">
            <div class="mb-4">
              <p class="text-xl leading-tight">Adam Wathan</p>
              <p class="text-sm leading-tight text-grey-dark">Developer at NothingWorks Inc.</p>
            </div>
            <div>
              <button class="text-xs font-semibold rounded-full px-4 py-1 leading-normal bg-white border border-purple text-purple hover:bg-purple hover:text-white">Message</button>
            </div>
          </div>
        </div>
      </div>
    </element-query>
`
})

new Vue({ el: '#app' })