Every persistence is the accumulation of success, as long as you believe in yourself, there will always be surprises 😜

preface

When we chat with QQ, Ctrl+C a picture from another place, and then Ctrl+V in the chat window, QQ will paste the picture you just copied into the message container to be sent, press Enter, this picture will be sent. Let me share with you how this feature is implemented in Vue. Just to show you what the final result looks like. Online Experience Address

Implementation approach

  • Listen for clipboard paste events while the page is mounted
  • Listening for file flow
  • Read data from a file stream
  • Create an IMG tag
  • Assign the base64 code obtained to the SRC attribute of the IMG tag
  • Append the generated IMG tag to the message container to be sent
  • Listen for carriage return events
  • Gets all child elements in an editable div container
  • Iterate over the retrieved elements to find the IMG element
  • Determine if the current IMG element has an Alt attribute.
  • If there is no Alt attribute, the current element is the image
  • Upload base64 images as files to the server
  • After successful upload, the image address returned by the server is pushed to the Websocket service
  • When the client receives the push, it renders the page

The implementation process

This article mainly explains how to parse clipboard pictures and convert Base64 pictures into files and upload them to the server. For the encapsulation of AXIOS and the configuration and use of Websocket in the code below, please refer to my other two articles: Vue reasonably configures AXIOS and carries out practical application in the project. Vue reasonably configures WebSocket and realizes group chat

  • Listens for clipboard events (mounted life cycle) to render images to be sent to the message container

    const that = this;
    document.body.addEventListener('paste'.function (event) {
        / / write a full-screen loading plug-ins, address: https://juejin.cn/post/6844904054343090183
        that.$fullScreenLoading.show("Read in the picture");
        // Get the text in the current input box
        const oldText = that.$refs.msgInputContainer.textContent;
        // Read the image
        let items = event.clipboardData && event.clipboardData.items;
        let file = null;
        if (items && items.length) {
            // Retrieve clipboard items
            for (let i = 0; i < items.length; i++) {
                if (items[i].type.indexOf('image')! = =- 1) {
                    file = items[i].getAsFile();
                    break; }}}// Preview the image
        const reader = new FileReader();
        reader.onload = function(event) {
            // Image content
            const imgContent = event.target.result;
            // Create the img tag
            let img = document.createElement('img');// Create an img
            // Get the current base64 image information, calculate the current image width, height and compression ratio
            let imgObj = new Image();
            let imgWidth = "";
            let imgHeight = "";
            let scale = 1;
            imgObj.src = imgContent;
            imgObj.onload = function() {
                // Calculate img width and height
                if(this.width<400){
                    imgWidth = this.width;
                    imgHeight = this.height;
                }else{
                    // The input box image display is reduced by 10 times
                    imgWidth = this.width/10;
                    imgHeight = this.height/10;
                    // The width of the image is greater than 1920, and the image is compressed 5 times
                    if(this.width>1920) {// The real scale is reduced by 5 times
                        scale = 5; }}// Set the width and height of images in editable div
                img.width = imgWidth;
                img.height = imgHeight;
                // Compress the image and render the page
                that.compressPic(imgContent,scale,function (newBlob,newBase) {
                    // Delete the image name in the editable div
                    that.$refs.msgInputContainer.textContent = oldText;
                    img.src = newBase; // Set the link
                    // Image rendering
                    that.$refs.msgInputContainer.append(img);
                    that.$fullScreenLoading.hide();
                });
            };
        };
        reader.readAsDataURL(file);
    });
    Copy the code
  • Base64 image compression function

    // Parameters: base64 address, compression ratio, callback function (return blob and Base64 of compressed image)
    compressPic:function(base64, scale, callback){
        const that = this;
        let _img = new Image();
        _img.src = base64;
        _img.onload = function() {
            let _canvas = document.createElement("canvas");
            let w = this.width / scale;
            let h = this.height / scale;
            _canvas.setAttribute("width", w);
            _canvas.setAttribute("height", h);
            _canvas.getContext("2d").drawImage(this.0.0, w, h);
            let base64 = _canvas.toDataURL("image/jpeg");
            // Add toBlob methods manually when there are no toBlob methods in the canvas prototype
            if(! HTMLCanvasElement.prototype.toBlob) {Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
                    value: function (callback, type, quality) {
                        let binStr = atob(this.toDataURL(type, quality).split(', ') [1]),
                            len = binStr.length,
                            arr = new Uint8Array(len);
                        for (let i = 0; i < len; i++) {
                            arr[i] = binStr.charCodeAt(i);
                        }
                        callback(new Blob([arr], {type: type || 'image/png'})); }}); }else{
                _canvas.toBlob(function(blob) {
                    if(blob.size > 1024*1024){
                        that.compressPic(base64, scale, callback);
                    }else{ callback(blob, base64); }},"image/jpeg"); }}}Copy the code
  • Improve the message sending function, obtain all the child elements in the input box, find out the Base64 picture, convert it into a file and upload it to the server (note: when base64 is transferred to a file, the prefix of base64 picture needs to be deleted with regular expression), and push the current picture address to the Websocket service.

    If you don’t understand the following code, please read my other article: Vue implements image and text mixing,

    sendMessage: function (event) {
        if (event.keyCode === 13) {
            // Prevents edit boxes from generating div events by default
            event.preventDefault();
            let msgText = "";
            // Get all the children of the input box
            let allNodes = event.target.childNodes;
            for (let item of allNodes) {
                // Determine if the current element is an IMG element
                if (item.nodeName === "IMG") {
                    if (item.alt === "") {
                        / / is pictures
                        let base64Img = item.src;
                        // Remove the base64 image prefix
                        base64Img = base64Img.replace(/^data:image\/\w+; base64,/."");
                        // Random file name
                        let fileName = (new Date()).getTime() + ".jpeg";
                        // Convert base64 to file
                        let imgFile = this.convertBase64UrlToImgFile(base64Img, fileName, 'image/jpeg');
                        let formData = new FormData();
                        // The property of file here is the same as the property of background value. The file name must be added in append, otherwise the blob will always be added
                        formData.append('file', imgFile, fileName);
                        // Upload the image to the server
                        this.$api.fileManageAPI.baseFileUpload(formData).then((res) = > {
                            const msgImgName = ` /${res.fileName}/ `;
                            // Send a message: send a picture
                            this.$socket.sendObj({
                                msg: msgImgName,
                                code: 0.username: this.$store.state.username,
                                avatarSrc: this.$store.state.profilePicture,
                                userID: this.$store.state.userID
                            });
                            // Empty the input field
                            event.target.innerHTML = "";
                        });
                    } else {
                        msgText += ` /${item.alt}/ `; }}else {
                    // Get the value of the text node
                    if(item.nodeValue ! = =null) { msgText += item.nodeValue; }}}// Send message: send text, if empty, do not send
            if (msgText.trim().length > 0) {
                this.$socket.sendObj({
                    msg: msgText,
                    code: 0.username: this.$store.state.username,
                    avatarSrc: this.$store.state.profilePicture,
                    userID: this.$store.state.userID
                });
                // Empty the input field
                event.target.innerHTML = ""; }}}Copy the code
  • Base64 image to FLIE

    / / base64 file
    convertBase64UrlToImgFile: function (urlData, fileName, fileType) {
        // Convert to byte
        let bytes = window.atob(urlData);
        // Handle exceptions to convert ASCII codes less than 0 to greater than 0
        let ab = new ArrayBuffer(bytes.length);
        let ia = new Int8Array(ab);
        for (let i = 0; i < bytes.length; i++) {
            ia[i] = bytes.charCodeAt(i);
        }
        // Convert to a file and add the type, name, and lastModifiedDate attributes to the file
        let blob = new Blob([ab], {type: fileType});
        blob.lastModifiedDate = new Date(a); blob.name = fileName;return blob;
    }
    Copy the code
  • Encapsulate the axios file upload interface (note :” content-type “:”multipart/form-data”} is required)

    /* * File management interface * */
    import services from '.. /plugins/axios'
    import base from './base'; // Import the interface domain name list
    
    const fileManageAPI = {
        // Upload a single file
        baseFileUpload(file){
            return services._axios.post(`${base.lkBaseURL}/uploads/singleFileUpload`,file,{headers: {"Content-Type":"multipart/form-data"}}); }};export default fileManageAPI;
    
    Copy the code
  • Parse webSocket push messages

    // Message parsing
    messageParsing: function (msgObj) {
        // Parse the data returned by the interface and render it
        let separateReg = /(\/[^/]+\/)/g;
        let msgText = msgObj.msgText;
        let finalMsgText = "";
        // Place the qualified strings in the array
        const resultArray = msgText.match(separateReg);
        if(resultArray ! = =null) {
            for (let item of resultArray) {
                // Remove the/symbol from the string
                item = item.replace(/\//g."");
                // Determine whether it is a picture: the suffix is.jpeg
                if(this.isImg(item)){
                    // parse to an img tag
                    const imgTag = `<img src="${base.lkBaseURL}/upload/image/${item}"Alt =" Chat image "> ';
                    // Replace the matching string with the IMG tag: global replace
                    msgText = msgText.replace(new RegExp(` /${item}/ `.'g'), imgTag);
                }
            }
            finalMsgText = msgText;
        } else {
            finalMsgText = msgText;
        }
        msgObj.msgText = finalMsgText;
        // Render the page
        this.senderMessageList.push(msgObj);
        // Change the scroll bar position
        this.$nextTick(function () {
            this.$refs.messagesContainer.scrollTop = this.$refs.messagesContainer.scrollHeight;
        });
    }
    Copy the code
  • Determines whether the current string has a picture suffix

    // Check if it is a picture
    isImg: function (str) {
        let objReg = new RegExp("[.]+(jpg|jpeg|swf|gif)$"."gi");
        return objReg.test(str);
    }
    Copy the code

Record on pit

  • Send base64 images directly to the server through websocket

    The server websocket service reported an error because the content exceeded the maximum length.

  • The front-end sends the Base64 code to the server through POST request, and the server directly parses the Base64 code into a picture and saves it to the server

    From 2pm to 6pm, I have been looking for the solution of Java parsing Base64 pictures to be stored in the server. Finally, I chose to give up and adopted the front-end conversion mode. The problem here is probably that when the front-end transmits base64 codes to the back-end, the HTTP request will be escaped, resulting in the base64 codes resolved by the back-end is wrong. So it never worked out.

  • Project address: chat-system

Write in the last

  • If there are any errors in this article, please correct them in the comments section. If this article helped you, please like it and follow 😊
  • This article was first published in the Nuggets. If you need to reprint it, please leave a comment at 💌