DEV Community

Cover image for Editing Images Before Uploading in Vue JS Project
Lakh Bawa
Lakh Bawa

Posted on

5

Editing Images Before Uploading in Vue JS Project

Hello Everyone, hope you are doing well and staying safe,

Today I will be sharing the small coding snippet that I created today to allow the users to edit/crop/rotate images before uploading them to the website.

I am using Vuetify with Material Icons in my project and assume many of you use that as well as it is the one that's most popular.

Alt Text

The most interesting point in this tutorial is, Edited image will be simply HTML File Object, unlike other solutions available out there on the internet which returns bash string or blob, etc, where you have to modify your backend logic.

so in our case, it would be fully compatible with the existing backend API uploading logic as it returns Default File Object.

First Install the dependency required to edit the image.

Installation

npm install --save vue-cropperjs

Secondly, create a component named AvatarUpload in the components folder or somewhere else where you feel comfortable.

In my case, its located at /components/Utils/AvatarUpload in my nuxtjs based project


<template>
  <div>
    <div class="content">
      <v-dialog v-model="isAvatarUploadDialogOpen" persistent max-width="600">
        <v-card>
          <v-card-title class="headline"></v-card-title>

          <v-card-text>
            <v-row no-gutters>
              <v-col class="">
                <section class="cropper-area">
                  <div class="img-cropper">
                    <vue-cropper
                      ref="cropper"
                      :initial-aspect-ratio="1"
                      :src="imgSrc"
                      preview=".preview"
                    />
                  </div>
                  <div class="actions mt-2">
                    <v-btn class="ma-1" small dark color="grey darken-3" @click.prevent="zoom(0.2)">
                      <v-icon> mdi-magnify-plus-outline</v-icon>
                    </v-btn>
                    <v-btn class="ma-1" small dark color="grey darken-3" @click.prevent="zoom(-0.2)">
                      <v-icon> mdi-magnify-minus-outline</v-icon>
                    </v-btn>
                    <v-btn class="ma-1" small dark color="grey darken-3" @click.prevent="rotate(90)">
                      <v-icon href="#">
                        mdi-axis-x-rotate-clockwise
                      </v-icon>
                    </v-btn>
                    <v-btn class="ma-1" small dark color="grey darken-3" @click.prevent="rotate(-90)">
                      <v-icon>
                        mdi-axis-x-rotate-counterclockwise
                      </v-icon>
                    </v-btn>
                    <v-btn class="ma-1" small dark color="grey darken-3" @click.prevent="move(10, 0)">
                      <v-icon>
                        mdi-arrow-left
                      </v-icon>
                    </v-btn>
                    <v-btn class="ma-1" small dark color="grey darken-3" @click.prevent="move(-10, 0)">
                      <v-icon>
                        mdi-arrow-right
                      </v-icon>
                    </v-btn>
                    <v-btn class="ma-1" small dark color="grey darken-3" @click.prevent="move(0, 10)">
                      <v-icon>
                        mdi-arrow-up
                      </v-icon>
                    </v-btn>
                    <v-btn class="ma-1" small dark color="grey darken-3" @click.prevent="move(0, -10)">
                      <v-icon>
                        mdi-arrow-down
                      </v-icon>
                    </v-btn>


                    <!--              <v-btn class="ma-1" small dark color="grey darken-3"  @click.prevent="cropImage">-->
                    <!--                <v-icon > mdi-crop</v-icon>-->
                    <!--              </v-btn>-->
                    <v-btn class="ma-1" small dark color="grey darken-3">
                      <v-icon @click.prevent="reset"> mdi-lock-reset</v-icon>
                    </v-btn>
                  </div>
                </section>
                <div class="text-right">
                  <v-btn small dark color="grey darken-3 mt-3" @click.prevent="submitImage">
                    <v-icon> mdi-send</v-icon>
                    Submit
                  </v-btn>
                </div>


                <!--            <div class="mt-4">-->
                <!--              <v-btn small dark color="grey darken-3" @click.prevent="showFileChooser">-->
                <!--                <v-icon>-->
                <!--                  mdi-upload-->
                <!--                </v-icon>-->
                <!--                Upload Image-->
                <!--              </v-btn>-->
                <!--            </div>-->

                <!-- <textarea v-model="data" /> -->

              </v-col>

              <!--        <v-col cols="12" sm="6">-->
              <!--          <section class="preview-area">-->
              <!--            &lt;!&ndash;            <p>Preview</p>&ndash;&gt;-->
              <!--            <div class="preview"/>-->
              <!--            <div class="cropped-image">-->
              <!--              <img v-if="cropImg" :src="cropImg" alt="Cropped Image"/>-->
              <!--              <div v-else class="crop-placeholder"/>-->
              <!--            </div>-->
              <!--          </section>-->
              <!--        </v-col>-->
            </v-row>

          </v-card-text>
        </v-card>
      </v-dialog>

    </div>
  </div>
</template>

<script>
import VueCropper from "vue-cropperjs";
import "cropperjs/dist/cropper.css";

export default {
  components: {
    VueCropper,
  },
  props: {
    // eslint-disable-next-line vue/require-prop-types
    image: {
      required: true
    },

  },
  data() {
    return {
      imgSrc: "/assets/images/berserk.jpg",
      cropImg: "",
      data: null,
      filename: "",
      mimeType: "",
      isAvatarUploadDialogOpen: false,
    };
  },
  watch: {
    image(value) {
      this.initialImageSetUp(value)
    }
  },
  mounted() {
    this.initialImageSetUp(this.image)
  },
  methods: {
    initialImageSetUp(value) {
      const self = this

      if (!value) {
        this.cropImg = ""
        this.$emit('close', true)
        return;
      }
      this.isAvatarUploadDialogOpen = true
      self.filename = value.name
      self.mimeType = value.type
      self.setImage(value)
    },
async dataURLToFile(imageString, filename, mimeType) {

    const res = await fetch(imageString);
    const blob = await res.blob();
    return new File([blob], filename, { type: mimeType });
  },
    async submitImage() {
      await this.cropImage();
      const imageFileResponse = await this.dataURLToFile(this.cropImg, this.filename, this.mimeType)
      this.$emit('done', imageFileResponse)
      this.isAvatarUploadDialogOpen = false

    },
    cropImage() {
      // get image data for post processing, e.g. upload or setting image src
      this.cropImg = this.$refs.cropper.getCroppedCanvas().toDataURL();
    },
    // flipX() {
    //   const dom = this.$refs.flipX;
    //   let scale = dom.getAttribute("data-scale");
    //   scale = scale ? -scale : -1;
    //   this.$refs.cropper.scaleX(scale);
    //   dom.setAttribute("data-scale", scale);
    // },
    // flipY() {
    //   const dom = this.$refs.flipY;
    //   let scale = dom.getAttribute("data-scale");
    //   scale = scale ? -scale : -1;
    //   this.$refs.cropper.scaleY(scale);
    //   dom.setAttribute("data-scale", scale);
    // },
    getCropBoxData() {
      this.data = JSON.stringify(this.$refs.cropper.getCropBoxData(), null, 4);
    },
    getData() {
      this.data = JSON.stringify(this.$refs.cropper.getData(), null, 4);
    },
    move(offsetX, offsetY) {
      this.$refs.cropper.move(offsetX, offsetY);
    },
    reset() {
      this.$refs.cropper.reset();
    },
    rotate(deg) {
      this.$refs.cropper.rotate(deg);
    },
    setCropBoxData() {
      if (!this.data) return;

      this.$refs.cropper.setCropBoxData(JSON.parse(this.data));
    },
    setData() {
      if (!this.data) return;

      this.$refs.cropper.setData(JSON.parse(this.data));
    },
    setImage(file) {

      if (!file.type.includes("image/")) {
        alert("Please select an image file");
        return;
      }

      if (typeof FileReader === "function") {
        const reader = new FileReader();

        reader.onload = (event) => {
          this.imgSrc = event.target.result;
          // rebuild cropperjs with the updated source
          this.$refs.cropper.replace(event.target.result);
        };

        reader.readAsDataURL(file);
      } else {
        alert("Sorry, FileReader API not supported");
      }
    },
    showFileChooser() {
      this.$refs.input.click();
    },
    zoom(percent) {
      this.$refs.cropper.relativeZoom(percent);
    },
  },
};
</script>

<style>
input[type="file"] {
  display: none;
}

.cropped-image {
  padding: 0 .8rem;
}

.img-cropper {
  max-height: 400px;
  overflow: hidden;
}

</style>
Enter fullscreen mode Exit fullscreen mode

Using AvatarUpload Component

In any component where you want to implement the ImageEditing Features. use it like this


<template>
  <div>
    <v-container>
      <v-file-input
        v-model="avatarImage"
        label="File input"
      ></v-file-input>
      <AvatarUpload :image="avatarImage" @done="(image) => form.image=image"/>
    </v-container>
  </div>
</template>

<script>
import AvatarUpload from "@/components/Utils/AvatarUpload";

export default {
  components: {AvatarUpload},
  data() {
    return {
      avatarImage: null,
      form: {
        image: null
      }
    }
  },
}
</script>

<style scoped></style>
Enter fullscreen mode Exit fullscreen mode
👋 Kindness is contagious

Please leave your appreciation by commenting on this post!

It takes one minute and is worth it for your career.

Get started

Thank you!

Top comments (0)

👋 Kindness is contagious

Discover a treasure trove of wisdom within this insightful piece, highly respected in the nurturing DEV Community enviroment. Developers, whether novice or expert, are encouraged to participate and add to our shared knowledge basin.

A simple "thank you" can illuminate someone's day. Express your appreciation in the comments section!

On DEV, sharing ideas smoothens our journey and strengthens our community ties. Learn something useful? Offering a quick thanks to the author is deeply appreciated.

Okay