15

I'm working on an application that uses Raphael to draw primitive shapes (rectangles, ellipses, triangles etc) and lines but allows the user to move/resize these objects as well. One of the main requirements is that the face of shapes can have formatted text. The actual text is a subset of Markdown (simple things like bolding, italics, lists) and is rendered as HTML.

FWIW - I'm using Backbone.js views to modularize the shape logic.

Approach 1

My initial thought was to use a combination of foreignObject for SVG and direct HTML with VML for IE. However, IE9 doesn't support foreignObject, and therefore this approach had to be abandoned.

Approach 2

With the beside the canvas object, add divs that contain the actual HTML. Then, position them over the actual shape with a transparent background. I've created a shape view that has references to both the actual Raphael shape and the "overlay" div. There are a couple of problems with this approach:

  1. Using overlay that aren't children of the SVG/VML container feels wrong. Does having this overlay element cause other issues with rendering down the road?

  2. Events that are normally trapped by Raphael (drag, click etc) need to be forwarded from the overlay to the Raphael object. For browsers that support pointer-events, this is easily done:

    div.shape-text-overlay {
      position: absolute;
      background: none;
      pointer-events: none;
    }
    

    However, other browsers (like IE8 and below) need forwarding of the events:

    var forwardedEvents = 'mousemove mousedown mouseup click dblclick mouseover mouseout';
    this.$elText.on(forwardedEvents, function(e) {
    
      var original = e.originalEvent;
    
      var event;
      if (document.createEvent) {
        event = document.createEvent('HTMLEvents');
        event.initEvent(e.type, true, true);
      } 
      else {
        event = document.createEventObject();
        event.eventType = e.type;
      }
    
      // FYI - This is the most simplistic approach to event forwarding. 
      // My real implementation is much larger and uses MouseEvents and UIEvents separately.
    
      event.eventName = e.type;
      _.extend(event, original);
    
      if (document.createEvent) {
        that.el.node.dispatchEvent(event);
      } 
      else {
        that.el.node.fireEvent('on' + event.eventType, event);
      }
    
    });
    
  3. Overlapping shapes cause the text to be overlapped because the text/shapes are on different layers. Although overlapping shapes won't be common, this looks bad:

    overlap

This approach is what I'm currently using but it just feels like a huge hack.

Approach 3

Almost like Approach 1, but this would involve writing text nodes/VML nodes directly. The biggest problem with this is the amount of manual conversion necessary. Breaking outside of Raphael's API seems like it could cause stability issues with the other Raphael objects.

Question

Has anyone else had to do something similar (rendering HTML inside of SVG/VML)? If so, how did you solve this problem? Were events taken into account?

TheCloudlessSky
  • 18,608
  • 15
  • 75
  • 116

2 Answers2

4

I built this project (no longer live) using Raphael. What I did is actually abandoned the idea of using HTML inside of SVG because it was just too messy. Instead, I absolutely positioned an HTML layer on top of the SVG layer and moved them around together. When I wanted the HTML to show, I merely faded it in and faded the corresponding SVG object out. If timed and lined up correctly, it's not really noticeable to the untrained eye.

While this may not necessarily be what you're looking for, maybe it will get your mind thinking of new ways to approach your problem! Feel free to look at the JS on that page, as it is unminified ;)

PS, the project is a lead-gathering application. If you just want to see how it works, select "Friend" in the first dropdown and you don't have to provide any info.

Jason
  • 51,583
  • 38
  • 133
  • 185
  • Slick site. Your approach is pretty much what I'm doing now for #2. The "bad" thing about it *for me* is that if the user moves two shapes on top of each other, you get the overlap as I showed in the OP. The other problem is the requirement to forward events from the overlay element to the Raphael object. But thanks for showing this because it shows exactly the road I was heading down. – TheCloudlessSky May 09 '12 at 23:27
  • Yeah, I figured it was similar to your #2. I didn't have to worry about collisions or events being passed back to the SVG, but you can avoid the overlapping by messing with `z-index` and putting a background color on your html elements (probably the same as the corresponding SVG object) if that would work. Of course I don't know much else about your project, but that's how I would handle it given the info supplied :) – Jason May 09 '12 at 23:33
0

Unless another answer can be provided and trump my solution, I have continued with the extra div layer. Forwarding events was the most difficult part (if anyone requires the forwarding events code, I can post it here). Again, the largest downside to this solution is that overlapping shapes will cause their text to overlap above the actual drawn shape.

TheCloudlessSky
  • 18,608
  • 15
  • 75
  • 116