4

I have text DIV's like below. I am targeting only non IE and webkit browsers.

One :

<div id="target1"> Some<sup>en</sup> <i><b>text</b></i> </div>

The above line looks like : Someen text with style

Second :

<div id="target2"> Some<sup>en</sup> <span class='boldIt'>text</span></div>
.boldIt{
   font-weight : bold;
   font-style : italic;
}

It looks same as above. According to view both are same. But below is different from above

<div id="target3"> Someen text</div>

My requirement is to compare three target elements.

What I have tried: I have tried iterating through each child node of DIV and and matching styles using getComputedStyle(). But I could see a big performance jerk happening on page. Because there are so many cases involved in this.

Have an idea, but helpless, Here is an IDEA Draw two elements on canvas and compare two canvas like comparing two images. Is this really possible? If possible when I am comparing one inline element with block level element, does it change the behavior of comparison?

Please suggest me on this. If no ideas I would stick to HTML way as I have mentioned before.

I have tried this using canvas and my target is only Chrome. Antony's answer is exactly matching with my requirement. But in below case I could not find a way to do that

for(var i = 0; i < arr.length; i++){
  var htStr = arr[i].one, htStr2 = arr[i].two;
  // How can I match these two now...
} 
Community
  • 1
  • 1
Exception
  • 8,111
  • 22
  • 85
  • 136
  • Why do you need to compare them? – Blender May 02 '13 at 06:46
  • 2
    It is like trying to do something which is interesting to me and to learn something :) – Exception May 02 '13 at 06:48
  • Is comparing HTML strings even considered as an option? – dfsq May 05 '13 at 14:02
  • So, in your example, are you considering target1 & target2 the same or not? – A. Wolff May 05 '13 at 14:04
  • @dsfq Style can be given in many ways, is it really possible to compare strings to validate this? – Exception May 05 '13 at 14:04
  • @roasted Yes, I want to compare styles also. I have tried to compare using Canvas elements like http://stackoverflow.com/questions/16337993/trying-to-compare-two-canvas-elements And I am unable to do that too.. – Exception May 05 '13 at 14:05
  • @Exception Is a Chrome extension-specific solution also acceptable? If yes, mention that in your question. – Rob W May 05 '13 at 14:34
  • @RobW I have mentioned in last line of my Question – Exception May 06 '13 at 06:27
  • @Exception In a Chrome extension, you can use the [`chrome.tabs.captureVisibleTab`](https://developer.chrome.com/extensions/tabs.html#method-captureVisibleTab) method to make the screenshot. This result is way more accurate than the html2canvas method, so if you want a Chrome extension-only solution, this is the way to go. – Rob W May 06 '13 at 12:10

2 Answers2

3

Your idea of using a <canvas> is possible.

See DEMO (tested on Chrome, Safari and Opera).

You can convert the following into 3 different canvases using html2canvas, and compare their base64 string to determine if they are visually the same. You should wrap them in separate containers so that all the texts are rendered in the canvases.

<div id="target1-container">
    <div id="target1"> Some<sup>en</sup> <i><b>text</b></i> </div>
</div>
<div id="target2-container">
    <div id="target2"> Some<sup>en</sup> <span class='boldIt'>text</span></div>
</div>
<div id="target3-container">
    <div id="target3"> Someen text</div>
</div>

Converting to canvas and base64 strings, using async.js to handle multiple asynchronous calls:

async.parallel({
    target1: function (callback) {
        html2canvas(document.getElementById("target1-container"), {
            onrendered: function (canvas) {
                callback(null, canvas.toDataURL());
            }
        });
    },
    target2: function (callback) {
        html2canvas(document.getElementById("target2-container"), {
            onrendered: function (canvas) {
                callback(null, canvas.toDataURL());
            }
        });
    },
    target3: function (callback) {
        html2canvas(document.getElementById("target3-container"), {
            onrendered: function (canvas) {
                callback(null, canvas.toDataURL());
            }
        });
    }
},
function (err, results) {
    console.log(results);
});

Regarding your updated question, here's a function to compare the visual appearance of elements defined by the strings:

function compare(arr, fn) {
    // create test div
    var testdiv = document.createElement("div");
    testdiv.style.marginTop = '1000px';
    document.body.appendChild(testdiv);
    async.map(
        arr,
        function (data, callback) {
            var htStr = data.one,
                htStr2 = data.two;
            // create elements from strings
            var el = document.createElement("div"),
                el2 = document.createElement("div");
            el.innerHTML = htStr;
            testdiv.appendChild(el);
            el2.innerHTML = htStr2;
            testdiv.appendChild(el2);
            // convert to canvas and base64 strings
            async.parallel([
                function (callback) {
                    html2canvas(el, {
                        onrendered: function (canvas) {
                            callback(null, canvas.toDataURL());
                        }
                    });
                },
                function (callback) {
                    html2canvas(el2, {
                        onrendered: function (canvas) {
                            callback(null, canvas.toDataURL());
                        }
                    });
                }
            ],
            // start comparison
            function (err, results) {
                if (results[0] === results[1]) {
                    callback(null, true);
                } else {
                    callback(null, false);
                }
            });
        },
        // callback function
        function (err, results) {
            document.body.removeChild(testdiv);
            if (typeof fn === 'function') {
                fn(results);
            }
        }
    );
}
// elements to be tested
var arr = [];
arr.push({
    one: '<div id="target1"> Some<sup>en</sup> <i><b>text</b></i> </div>',
    two: '<div id="target2"> Some<sup>en</sup> <span class="boldIt">text</span></div>'
});
arr.push({
    one: '<div id="target1"> Some<sup>en</sup> <i><b>text</b></i> </div>',
    two: '<div id="target3"> Someen text</div>'
});
// let the test begin
compare(arr, function (result) {
    console.log(result);
});

It returns an array in a callback function, with

  • true meaning the elements being visually the same, and
  • false meaning they being different.

See the console in this DEMO.

Antony
  • 14,900
  • 10
  • 46
  • 74
  • @Antony for me gives two different base64 images for target1 & target2 under chrome26 win7 – A. Wolff May 05 '13 at 14:40
  • It is working on my chrome.. and I am targeting only Chrome for now :) – Exception May 05 '13 at 14:41
  • @roasted Just checked on Chrome 26.0.1410.64 m on win7 and it works. – Antony May 05 '13 at 14:53
  • I've same chrome version, i need to check what's going on. – A. Wolff May 05 '13 at 14:58
  • This is the tag which make it buggy for me. Using anything else seems to work, even using a tag. Strange! – A. Wolff May 05 '13 at 15:09
  • Tested from an other computer win7 chrome (26.0.1410.64 m) give me expected result. – A. Wolff May 05 '13 at 19:10
  • @Antony For example I have two strings coming from JavaScript object and I do not have any third variable like results, because in for loop I will get two strings for each iteration to compare. – Exception May 06 '13 at 07:15
  • @Exception Can you explain that further? – Antony May 06 '13 at 07:17
  • @Antony Updated the question. Please see at the end of the question. – Exception May 06 '13 at 08:00
  • @Antony It seems like Canvas is not reliable. Is the same thing possible through DOM matching? I just want to match formatting of two elements. I do not need to compare any other styles except basic bold, italic, underline etc.. formatting – Exception May 06 '13 at 08:20
  • @Exception I am working on a solution for your updated question. As for DOM matching, since it involves style, it may be more complicated, but I will take a look at that as well. – Antony May 06 '13 at 08:32
  • @Antony I am waiting to accept your answer :) Thanks alot for your help – Exception May 06 '13 at 09:08
  • @Exception Just to clarify, do you mean to compare strings like `arr[i].one = '
    Someen text
    '` to `arr[i].two = '
    Someen text
    `?
    – Antony May 06 '13 at 09:49
  • I just wanted to compare view of two elements. Initially I am just targeting only bold and italics are same in two elements.. – Exception May 06 '13 at 10:09
  • @Antony Thanks, I will check and will let you know – Exception May 06 '13 at 10:42
  • 1
    @Antony I have tried your solution and it is working fine for i,sup,sub,u tags as there will be pixel level difference in the canvas. But it is failing if one text has bold and another has no bold. Please check here http://jsfiddle.net/xJ52P/5/. Thanks alot for your help. – Exception May 07 '13 at 06:16
  • 1
    @Exception Fixed it. It appears that the `test div` cannot be moved out of the screen to the left or right. I now push it to the very bottom of the page. See http://jsfiddle.net/xJ52P/6/. – Antony May 07 '13 at 06:42
  • 1
    @Antony A Small suggestion needed. Instead of logging just true or false I would like to display the text of the `el` or 'el2' is not matching visually... But in the code I could nod understand where to update the custom error message with proper comment for better results. – Exception May 07 '13 at 08:55
  • 1
    @Exception The reason I use `ture` or `false` is to make it easier to use the results. You do not have to modify the function, just changing the callback function would do. See http://jsfiddle.net/xJ52P/9/. – Antony May 07 '13 at 09:03
  • @Antony Thanks a lot for your effort and help – Exception May 07 '13 at 10:28
1

I'm assuming by "compare" you are referring to detecting whether the elements are rendered identically at the pixel level. Note that this condition could be satisfied by elements which are not at all equal on a lexical basis; conversely, two elements could be lexically identical yet render differently due to any number of factors, notably properties inherited from parents. You also need to take into account that fact that the pixel representation of an element in the sense of the pixels comprising its containing rectangle might be polluted by something somewhere else on the page; the obvious example is an element with a fixed position.

Given all the above, since we are defining equivalance as pixel-level equivalence, then the answer is indeed to take a "snapshot" of the elements to be compared and compare them. The easiest approach is to use a headless web browser such as PhantomJS. I will not provide a complete program, but here are the basics:

  1. In your page.evaluate function, obtain the dimensions of the element in question by using getBoundingClientRect.

  2. Then call back into PhantomJS using window.callPhantom. That will invoke a function defined using page.onCallBack.

  3. In the callback, set page.clipRect to the dimensions of the item you want to capture. Then use something like page.renderBase64 to capture the image, and store the result as a file.

  4. Repeat for the other element(s) you want to compare.

  5. Compare the stored base64 images for byte-level equivalence.

  • 1
    Thanks for your tip. But I am looking for pure JS solution as I am gonna use this in a Chrome extension. – Exception May 05 '13 at 14:12
  • Well then, say that. I suspect it's an NP-complete problem to determine by looking at HTML/CSS whether two elements will render the same. You have to render and compare pixels. You cannot do this within a browser with JS. You will pull your hair out trying to render HTML to a canvas. But see http://stackoverflow.com/questions/4912092/using-html5-canvas-javascript-to-take-screenshots/6678156#6678156, which I assume you already reviewed before posting your question. Note that facilities for doing this are available to extensions, as is obvious from the many extensions that do just that. –  May 05 '13 at 14:26