Blog

Posting a HTML5 Canvas image to Facebook and Twitter

by Jerez Bain on February 17, 2016 75 comments

Part of a recent project was to take an HTML 5 Canvas Image and post it to Facebook and Twitter. Although this sounds pretty straightforward, the challenge was that there would be no image saved to the server. I couldn’t just slap share buttons on the page, because share dialogs expect content that already exists on a server. Using Facebook’s API and Twitter’s API we are able to share rich dynamic content. Here’s a guide on what I did… (PHP, Javascript, jQuery, TwitterOAuth)

Code on Github

Canvas Image

Lets create a simple canvas element and add an image of a panda (we all like pandas, right?).

HTML
<canvas id="canvas" style="border:1px solid black;" width="256" height="256"></canvas>
JavaScript
// Canvas Object
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

// load image from data url
var imageObj = new Image();
imageObj.onload = function() {
ctx.drawImage(this, 0, 0);
};

imageObj.src = 'panda_dark.png';
Resulting Canvas

Post HTML5 Canvas Image on Facebook and Twitter_panda

Canvas to Base64 to Blob/Binary String to Social Media

In the following steps we will convert the canvas image to base64 by using .toDataURL() which is part of the Canvas API.

HTMLCanvasElement.toDataURL() Returns a data-URL containing a representation of the image in the format specified by the type parameter.

The function below will take the base64 string and convert it to a Blob/Binary String that will be sent to the Facebook and Twitter APIs. This function will be referenced later in the code.

Blob(blobParts[, options]) Returns a newly created Blob object whose content consists of the concatenation of the array of values given in parameter.

function dataURItoBlob(dataURI) {
    var byteString = atob(dataURI.split(',')[1]);
    var ab = new ArrayBuffer(byteString.length);
    var ia = new Uint8Array(ab);
    for (var i = 0; i < byteString.length; i++) {
        ia[i] = byteString.charCodeAt(i);
    }
    return new Blob([ab], {type: 'image/png'});
}

Posting to Facebook

Posting the image to Facebook will require creating a Facebook app which you will also need to request the ‘publish_actions’ permission when making the app live.  Now we create a ‘Post to Facebook’ button and link a click handler for the logic. Below you can see that we grab the base64 encoding of the canvas image that has been returned in the format of a .png image. We then try to convert the base64 string into a Blob. Using the Facebook JS SDK, after the user has logged in and given the app permission, the ‘postImageToFacebook’ function is called and passed several arguments.

$('#shareFB').click(function () {
    var data = $('#canvas')[0].toDataURL("image/png");
    try {
        blob = dataURItoBlob(data);
    } catch (e) {
        console.log(e);
    }
    FB.getLoginStatus(function (response) {
        console.log(response);
        if (response.status === "connected") {
            postImageToFacebook(response.authResponse.accessToken, "Canvas to Facebook/Twitter", "image/png", blob, window.location.href);
        } else if (response.status === "not_authorized") {
            FB.login(function (response) {
                postImageToFacebook(response.authResponse.accessToken, "Canvas to Facebook/Twitter", "image/png", blob, window.location.href);
            }, {scope: "publish_actions"});
        } else {
            FB.login(function (response) {
                postImageToFacebook(response.authResponse.accessToken, "Canvas to Facebook/Twitter", "image/png", blob, window.location.href);
            }, {scope: "publish_actions"});
        }
    });
});

Posting to the blob to facebook requires the use of FormData. The function first uploads the picture to facebook without creating a story on the user’s timeline. Then it retrieves the saved image and creates a story on the user’s timeline using the fields provided.
NOTE: The ‘message’ field must be left blank unless you provide the user a way to add one.

function postImageToFacebook(token, filename, mimeType, imageData, message) {
    var fd = new FormData();
    fd.append("access_token", token);
    fd.append("source", imageData);
    fd.append("no_story", true);

    // Upload image to facebook without story(post to feed)
    $.ajax({
        url: "https://graph.facebook.com/me/photos?access_token=" + token,
        type: "POST",
        data: fd,
        processData: false,
        contentType: false,
        cache: false,
        success: function (data) {
            console.log("success: ", data);

            // Get image source url
            FB.api(
                "/" + data.id + "?fields=images",
                function (response) {
                    if (response && !response.error) {
                        //console.log(response.images[0].source);

                        // Create facebook post using image
                        FB.api(
                            "/me/feed",
                            "POST",
                            {
                                "message": "",
                                "picture": response.images[0].source,
                                "link": window.location.href,
                                "name": 'Look at the cute panda!',
                                "description": message,
                                "privacy": {
                                    value: 'SELF'
                                }
                            },
                            function (response) {
                                if (response && !response.error) {
                                    /* handle the result */
                                    console.log("Posted story to facebook");
                                    console.log(response);
                                }
                            }
                        );
                    }
                }
            );
        },
        error: function (shr, status, data) {
            console.log("error " + data + " Status " + shr.status);
        },
        complete: function (data) {
            //console.log('Post to facebook Complete');
        }
    });
}
Resulting Post to Facebook
Post HTML5 Canvas Image on Facebook and Twitter_panda_fb

Posting to Twitter

Posting the image to twitter will also require you to create a Twitter App. Twitter still uses OAuth 1.0, so you will either need to use server-side code or a service such as ouath.io. Unless requested I’ll skip the authentication process here for TwitterOAuth which I’m using. The functions used below handle the popup that communicates with server-side code Credits to https://github.com/nobuf/jQuery-OAuth-Popup.

// Twitter oauth handler
$.oauthpopup = function (options) {
    if (!options || !options.path) {
        throw new Error("options.path must not be empty");
    }
    options = $.extend({
        windowName: 'ConnectWithOAuth' // should not include space for IE
        , windowOptions: 'location=0,status=0,width=800,height=400'
        , callback: function () {
            debugger;
            //window.location.reload();
        }
    }, options);

    var oauthWindow = window.open(options.path, options.windowName, options.windowOptions);
    var oauthInterval = window.setInterval(function () {
        if (oauthWindow.closed) {
            window.clearInterval(oauthInterval);
            options.callback();
        }
    }, 1000);
};
// END Twitter oauth handler

//bind to element and pop oauth when clicked
$.fn.oauthpopup = function (options) {
    $this = $(this);
    $this.click($.oauthpopup.bind(this, options));
};

Posting to Twitter is similar to Facebook in the case that you use FormData, but this is sent to your server-side handler or service instead. You can send the image as a Blob but this is not necessary. The variable window.twit contains authentication data sent from the popup window back to the parent webpage/tab.

$('#shareTW').click(function () {
    var dataURL = $('#canvas')[0].toDataURL("image/png");
    $.oauthpopup({
        path: '/auth/twitter.php',
        callback: function () {
            console.log(window.twit);
            var data = new FormData();
            // Tweet text
            data.append('status', "Look at the cute panda! " + window.location.href + " @jerezb31");
            // Binary image
            data.append('image', dataURL);
            // oAuth Data
            data.append('oauth_token', window.twit.oauth_token);
            data.append('oauth_token_secret', window.twit.oauth_token_secret);
            // Post to Twitter as an update with

            return $.ajax({
                url: '/auth/share-on-twitter.php',
                type: 'POST',
                data: data,
                cache: false,
                processData: false,
                contentType: false,
                success: function (data) {
                    console.log('Posted to Twitter.');
                    console.log(data);
                }
            });
        }
    });
});

Here is the snippet of server-side code that handles the FormData to be sent to twitter using TwitterOAuth. The credentials are verified then the base64 image is uploaded to Twitter’s API ‘upload/image’ endpoint. The object returned is then added to the list of parameters for posting a status.

// '/auth/share-on-twitter.php'

require_once '../vendor/abraham/twitteroauth/autoload.php';
use Abraham\TwitterOAuth\TwitterOAuth;

session_start();

define('CONSUMER_KEY', '*************');
define('CONSUMER_SECRET', '************');
define('OAUTH_CALLBACK', '*****************');

//echo '<pre>'.print_r($_REQUEST).'</pre>';
//exit;

$connection = new TwitterOAuth(CONSUMER_KEY, CONSUMER_SECRET, $_REQUEST['oauth_token'], $_REQUEST['oauth_token_secret']);
$twitterUser = $connection->get("account/verify_credentials");

$media1 = $connection->upload('media/upload', ['media' => $_REQUEST['image']]);
$parameters = [
    'status' => $_REQUEST['status'],
    'media_ids' => implode(',', [$media1->media_id_string]),
];
$result = $connection->post('statuses/update', $parameters);
echo json_encode($result);
Resulting post to Twitter

Post HTML5 Canvas Image on Facebook and Twitter_panda_tw

View the working Demo!

Code on Github

Jerez BainPosting a HTML5 Canvas image to Facebook and Twitter