Smoothing embedded Marketo form loads

When you use the default Forms 2.0 embed code, the form descriptor loads asynchronously. This means the inner HTML of the <form> tag isn't rendered until after the initial scaffolding of the page is finished drawing (often immediately after, but always after).

(The descriptor is the meat of the form config, managed in Form Editor: the fields, field order, validation rules, inter-field dependencies, custom CSS styles, all that stuff.)

Let me take care to say that this is a totally sensible default. Marketo can't know how many other asynchronous components are present on your page. You could have multiple content panes filled via Ajax, as and a bunch of other 3rd-party asynchronous widgets. In the absence of other info, and not knowing if the site is a complex corporate website or a simple (non-Marketo) LP, it makes sense to not have the form block other parts of the page from loading.

Nevertheless, in practice you may notice an uncomfortable reflow of other elements as the <form> builds out its content, bulging into a larger area of the page. Floats can shift to a new row and such.

Preallocate visual space

One of the simplest ways to ease the user experience is to wrap the <form> in a container that has a fixed width and height. This will completely eliminate the reflow, though it won't change the fact that the form loads asychronously and renders its inner HTML element-by-element.

Fade in the full form

Even better for reducing visual disruption (in addition to a preallocated container) is fading in the form after all its inner elements have been drawn.

Do this with a combo of CSS:

.mktoForm {
  opacity: 0;
  transition: opacity 100ms ease-out;
}
.mktoForm[data-mkto-ready="true"] {
  opacity: 1;
}

and Forms 2.0 API JS:

MktoForms2.whenReady(function(form) {
  form.getFormElem()[0].setAttribute("data-mkto-ready","true");
});

This technique is all but mandatory if you're using a lightboxed form, by the way, as the UX is really shoddy otherwise.

Bring out the big guns

If you're adventurous, you can load the form descriptor synchronously instead of async.

To do this, you call the /getForm endpoint manually, together with some other JS setup. This is a JSONP endpoint, not a JSON service — which is to say, it returns runnable JavaScript and is designed to be called as a <script src> (so it's not a hack to call it like this!).

When you set up the <script src> manually, you have to pre-URL-encode the url query parameter, like you see here:

//app-sj01.marketo.com/index.php/form/getForm?munchkinId=410-XOR-673&form=335&url=https%3A%2F%2Fwww.example.com%2Fpagename.html&callback=loadMktoFormSync

Set the url to any URL you want, even the dummy one I used above. It's supposed to represent the current page that's loading the form, but it isn't used anywhere AFAIK.

And note the required munchkinId and form params.

Here's a CodePen demo:

Loading the descriptor synchronously doesn't eliminate all the asynchronous actions associated with form rendering: because of the way Forms 2.0 uses events and timers, the event loop will still turn a few times as the form falls into place (meaning the rest of the page will continue drawing at the same time). But it does eliminate one major part of the out-of-order user experience.

I'm about to blow your mind

Separately loading the descriptor (whether synchronous or asynchronous, actually) allows you to do some pretty major things.

Feast your eyes on this:

   var mktoLanguages = [
      {
         lang: "en",
         fields: [
            {
               Name: "FirstName",
               InputLabel: "First Name:"
            },
            {
               Name: "Phone",
               InputLabel: "Phone Number:",
               FieldMask: "999-999-9999"
            }
         ]
      },
      {
         lang: "fr",
         fields: [
            {
               Name: "FirstName",
               InputLabel: "Prénom:"
            },
            {
               Name: "Phone",
               InputLabel: "Numéro de téléphone:",
               FieldMask: "09 99 99 99 99"
            }
         ]
      }
   ];
   
  function translateMktoDescriptor(descriptor, xlateLangs, session) {
  
    xlateLangs
      .find(function(language) {
        return language.lang == session.lang;
      })
      .fields.forEach(function(xlateField) {
        descriptor.rows.forEach(function(templateRow) {
          templateRow
            .filter(function(templateField) {
              return templateField.Name == xlateField.Name;
            })
            .forEach(function(templateField) {
              Object.assign(templateField, xlateField);
            });
        });
      });
  
    return descriptor;
  }

  var descriptor = translateMktoForm(descriptor, mktoLanguages, { lang: "fr" } );

Now you have language-specific forms with only one Marketo form to manage on the back end.