Kruse-Net.dk

Det man blogger er man selv...

Animerede SVG filer i IExplorer og Firefox

En rose lavet i SVG, er en rose lavet i SVG, er en…

Der er mange måder hvorpå man kan gøre den samme ting – men, desværre ikke altid én måde at gøre det samme på i henholdsvis IExplorer og Firefox. Det er ved at blive bedre, jo vist, men når man bevæger sig ud på gyngende grund, der hvor nye browserteknologier er ved at blive dannet og standardiserede, så ber’ man jo selv om problemer. “Nye” browserteknologier er af samme grund ofte længe undervejs, og det må formodes at SVG formattet ikke rigtigt har fået et fodfæste endnu, fordi der stadig er forskelle browserne imellem. Den første W3 recommended version af SVG undertegnede kunne finde frem til er dateret 2001. Lidt sørgeligt egentlig.

Nå, men jeg kunne jo ikke blive ved med at vente på at Adobe, Microsoft og Mozilla teamet bliver enige og tænkte at jeg derfor måtte tage skeen i egen hånd. Det er denne artikel så resultatet af. Det passer så ikke helt, jeg har ikke trasket rundt siden 2001 og bare ventet på en løsning, men et konkret behov i forbindelse med en programmeringsopgave gjorde, at NU skulle der altså ske noget.

Inline eller embedded

Den umiddelbare forskel man konfronteres med, er at Internet explorer har det bedst med embedded SVG, og at firefox stortrives med inline SVG.

Embedded SVG i Internet Explorer:
::HLIGHTBLOCK3::

Filen “cirkel.svg” som bruges i embed tagget:

::HLIGHTBLOCK4::

Inline SVG i firefox:
<object title="This page requires SVG support. If using Internet Explorer, go to http://www.adobe.com/svg/viewer/install/" id="AdobeSVG" classid="clsid:78156a80-c6a1-4bbf-8e6a-3cd390eeb4e2"> </object>
<?import namespace="svg" urn="http://www.w3.org/2000/svg" implementation="#AdobeSVG"?>
<svg:svg version="1.1" baseProfile="full" width="300px" height="300px"> 
 <svg:g id="cirkelGruppe">
   <svg:circle cx="150" cy="100" r="25" fill="red" id="cirkelObjekt" />
 </svg:g>
</svg:svg>

I skrivende stund har jeg ikke kigget på om inline SVG data kan bringes til at validere. Men det er klart, at <svg:svg ... > midt i et XHTML dokument ikke er helt efter valideringsreglerne. Jeg savner muligheden for helt at droppe <embed ... /> og inline <object ... /> mulighederne, og istedet have et standardiseret og cross-browser <svg src="..." /> tag tilrådighed.

Eksempel 1:
Se et eksempel på embedded SVG her, og et eksempel på inline SVG her.

Hvad er problemet?

Alt ser jo godt ud i de to eksempler herover – det virker fint i begge browsere. Så lad os straks smide et script på SVG objektet, så vi kan udnytte at både Firefox og Internet Explorer er nået langt med DOM manipulation via scripting, og dermed gøre det muligt at lave tricks og kunster med vores SVG grafik!

Eksempel 2:
Dette eksempel viser cross-browser problematikken, så læs det lige ind i Internet Explorer og Firefox.

Eksempel 2 er en embedded udgave af cirkel.svg hvor jeg har en javascript funktion alertId på XHTML hoveddokumentet, og har tilføjet en onclick eventlistener på cirkelObjekt‘et i SVG dokumentet. Det der sker er, at hvis man klikker på cirkelGruppe objektet, så kaldes alertId funktionen. Alert funktionen viser så en besked om hvad ID’et er på det objekt der gav anledning til at onclick event’en blev fyret af. Med ID’et på et DOM element i hånden har vi mere eller mindre fuld kontrol, og kan begynde at lave kunster. Men, vi bemærker desværre straks at alt virker fint i Internet Explorer og ikke i Firefox, der i dette tilfælde er tavs som graven. Jeg skal her lige tilføje at standardindstillingerne i Internet Explorer gør, at man skal klikke en gang på en SVG tegning med venstremussetast, mellemrum eller enter for at aktivere den – først herefter er det muligt at klikke på cirklen og se den ventede alert besked. Teksten “Tryk på mellemrum eller Enter for at aktivere og bruge dette objekt” vises over SVG tegningen indtil dette er sket.

alertId funktionen i hoveddokumentet:
<script type="text/ecmascript">
function alertId(oElement) {
  try {
    alert(event.srcElement.id);
  } 
  catch(e) {
    alert(oElement.id);
  }
}
</script>

Den embeddede SVG file der kalder alertId funktionen:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg:svg id="svgRod" xmlns:svg="http://www.w3.org/2000/svg" version="1.1" xml:lang="en-GB" viewBox="0 0 300 300" width="100%" height="100%">
 <svg:g id="cirkelGruppe" onclick="alertId(document.getElementById('cirkelGruppe'));">
   <svg:circle cx="150" cy="100" r="25" fill="red" id="cirkelObjekt" />
 </svg:g>
</svg:svg>

I Firefox kan man få hul igennem onclick event listeneren ved at droppe embed tagget og lægge SVG objektet ind inline:

Inline SVG der kalder alertId funktionen:
<object title="This page requires SVG support. If using Internet Explorer, go to http://www.adobe.com/svg/viewer/install/" id="AdobeSVG" classid="clsid:78156a80-c6a1-4bbf-8e6a-3cd390eeb4e2"> </object>
<?import namespace="svg" urn="http://www.w3.org/2000/svg" implementation="#AdobeSVG"?>
<svg:svg version="1.1" baseProfile="full" width="300px" height="300px"> 
 <svg:g id="cirkelGruppe" onclick="alertId(document.getElementById('cirkelGruppe'));">
   <svg:circle cx="150" cy="100" r="25" fill="red" id="cirkelObjekt" />
 </svg:g>
</svg:svg>

En fælles løsning

Nu har vi to måder, en til hver browser, der virker hver for sig. Og det store spørgsmål er om man nemt kan lave en fælles snitflade så man ikke behøver bekymre sig om forskellene.

Det der er behov for fra udviklerens synspunkt er at kunne smide en velstruktureret SVG fil efter en browser, og så være ligeglad med hvordan den integreres behind-the-scenes i browseren. Det er også generelt at man som udvikler vil have styr på width og height parametrene, så det skal der også tages højde for.

Så ganske kort, følgende funktion skal oprettes, der kan afklare hvilken browser klienten har, og derudfra selv indsætte SVG filen på siden sådan at den integreres korrekt og virker:

Simpel cross-browser API:
Function renderSvgFile(Byval svgFile, Byval width, Byval height)
  ...
End Function

Vi opretter straks en ASP klasse til formålet SVGAdaptiveDesignPattern. Denne klasse finder selv ud af givet en SVG fil at få SVG dataene trukket ind som embed eller via tilpassede inline data læst op og udledt fra SVG filen. (En ren javascript løsning er under udarbejdelse, således der ikke er afhængighed til ASP, PHP, JSP … men kan afvikles i alle serveropsætninger.)

Simpel cross-browser API - SVGAdaptiveDesignPattern.asp:
<%
Dim SVGAdaptiveDesignPatternAgentSupport
SVGAdaptiveDesignPatternAgentSupport = (InStr(LCase("" & _
  Request.ServerVariables("HTTP_ACCEPT")), "application/xhtml+xml") > 0)
If SVGAdaptiveDesignPatternAgentSupport Then
  Response.ContentType = "application/xhtml+xml"
End If

Dim SVGAdaptiveDesignPatternAgent
SVGAdaptiveDesignPatternAgent = Request.ServerVariables("http_user_agent") 

Class SVGAdaptiveDesignPattern

  Private beSilent
  
  ' Public -------------------------------------------------------------------
  Function setBeSilent(ByVal state)
    beSilent = state
  End Function 
  
  ' Public -------------------------------------------------------------------
  Function renderSvgFile(Byval svgFile, Byval width, Byval height)
  
    If InStr(LCase(SVGAdaptiveDesignPatternAgent),"firefox") Then 
      svgToFirefox svgFile, width, height
    ElseIf InStr(LCase(SVGAdaptiveDesignPatternAgent),"msie") Then
      svgToIExplorer svgFile, width, height
    Else
      Response.Write "SVGAdaptiveDesignPattern renderer not " & _
        "implemented for '" & SVGAdaptiveDesignPatternAgent & "'<br />" & vbCrLf
    End If    
    
  End Function
  
  ' Private -------------------------------------------------------------------
  Function svgToIExplorer(Byval svgFile, Byval width, Byval height)
    
    If Not beSilent Then
      Response.Write "SVGAdaptiveDesignPattern IExplorer renderer<br />" & vbCrLf
    End If
    Response.Write "<embed name=""EmbeddedSVG"" pluginspage=""" & _
       "http://www.adobe.com/svg/viewer/install/"" src=""" & svgFile & _
       """ width=""" & width & """ height=""" & height & "" & _
       " type=""image/svg-xml"" />" & vbCrLf      
  
  End Function
  
  ' Private -------------------------------------------------------------------
  Function svgToFirefox(Byval svgFile, Byval width, Byval height)
  
    If Not beSilent Then
      Response.Write "SVGAdaptiveDesignPattern Firefox renderer<br />" & vbCrLf
    End If
          
    Response.Write "<object title=""This page requires SVG support. If using Internet " & _
      "Explorer, go to http://www.adobe.com/svg/viewer/install/"" id=""AdobeSVG"" " & _
      "classid=""clsid:78156a80-c6a1-4bbf-8e6a-3cd390eeb4e2""> </object>" & vbCrLf

    Response.Write "<?import namespace=""svg"" urn=""http://www.w3.org/2000/svg"" " & _
      "implementation=""#AdobeSVG""?>" & vbCrLf
    
    Dim fs
    Dim f
    Set fs=Server.CreateObject("Scripting.FileSystemObject")          
    Set f=fs.OpenTextFile(Server.MapPath(svgFile), 1)
    Do While f.AtEndOfStream = false
      Dim linedata : linedata = f.ReadLine
      If InStr(UCase(linedata), "<?XML")<=0 Then 
        If InStr(UCase(linedata), "<!DOCTYPE")<=0 Then
          If InStr(LCase(linedata), "<svg:svg") > 0 Then
            linedata = "<svg:svg version=""1.1"" baseProfile=""full"" width=""" & width & _
              "px"" height=""" & height & "px""> "
          End If
          Response.Write(linedata)
          Response.Write(vbCrLf)
        End If
      End If
    Loop
    f.Close
    
    Set f=Nothing
    Set fs=Nothing

  End Function
  
End Class
%>

Vi kan nu oprette et simpel XHTML dokument, der helt selv finder ud af at integrere en ønsket SVG fil korrekt på siden uanset om klientens browser er Internet Explorer eller Firefox:

Cross-browser løsning – default.asp:
<% Option Explicit %><!-- #include file="SVGAdaptiveDesignPattern.asp" --><?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
  <head>
    <title>Embedded eller Inline SVG data i et XHTML dokument</title>
    <script type="text/ecmascript">
      function alertId(oElement) {
        try {
          alert(event.srcElement.id);
        } 
        catch(e) {
          alert(oElement.id);
        }
      }
    </script>
  </head>
  <body>
    <h1>Embedded eller Inline SVG data i et XHTML dokument</h1>
    <% Response.write Request.ServerVariables("http_user_agent") %><br />    
    <br />
    
    <table style="width:100%;background:#ededed;">
      <tr>
        <td style="text-align:center;border-top:1px solid black;border-bottom:1px solid black;">          
          <% 
          Dim svgRenderer
          Set svgRenderer = New SVGAdaptiveDesignPattern
          svgRenderer.renderSvgFile "cirkelScript.svg", 300, 300
          Set svgRenderer = Nothing
          %><br />
          <br />           
        </td>
      </tr>
    </table>
    
  </body>
</html>

Hvad kan det så bruges til?

At stå med et objekt ID i hånden åbner en masse døre. Jeg har her smidt et eksempel sammen der animerer tre forskellige SVG objekter på den samme side. Du skal blot klikke på objekterne.

Det er er sket siden sidste eksempel er, at der på onclick eventlisteners nu sparkes til en toggle funktion der afgører om objektet skal forstørres eller formindskes. Denne eventlistener ligger i SVG filen.

Animeret cross-browser eksempel – road.svg:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg:svg id="svg_root_road" xmlns:svg="http://www.w3.org/2000/svg" version="1.1" xml:lang="en-GB" viewBox="0 0 600 600" width="100%" height="100%">
 <script type="text/ecmascript"> 
<![CDATA[ 
var objectState = "small";
function toggleObject(svgObject) {
  if(objectState=="small") {
    enlargeIt(svgObject);
    objectState = "large";
  } else {
    shrinkIt(svgObject);
    objectState = "small";
  }
}
// ]]> 
</script>
 <svg:rect x="0" y="0" width="600" height="600" style="fill: #ededed;" />
 <svg:g id="roadlaneRightGroup" onclick="toggleObject(document.getElementById('roadlaneRightGroup'));">
   <svg:polyline points="100,100, 500,100, 500,200, 100,200, 100,100" style="stroke: black; fill: green;" id="theRoadRight" />
 </svg:g>
 <svg:g id="roadlaneLeftGroup" onclick="toggleObject(document.getElementById('roadlaneLeftGroup'));">
   <svg:polyline points="100,200, 500,200, 500,300, 100,300, 100,200" style="stroke: black; fill: blue;" id="theRoadLeft"/>
 </svg:g>
 <svg:circle cx="150" cy="100" r="25" fill="red" id="theCircle" onclick="toggleObject(document.getElementById('theCircle'));" />
 <svg:text x="160" y="65" style="text-anchor: middle;"> Click the circle </svg:text>
 <svg:text x="350" y="325" style="text-anchor: middle;"> Click the boxes </svg:text>
</svg:svg>

På XHTML dokumentet ligger der så funktionalitet til at animere de forskellige ønskede transformationer på objekterne. De er placeret i XHTML filen for at gøre det mere synligt at her ligger “timer” delen, som så vil kunne påvirkes af fx. input tags og/eller scripts herfra.

Animeret cross-browser eksempel – default.asp:
::HLIGHTBLOCK11::

Animeret cross-browser eksempel – default.js:
var targetScale = 10;
var currentScale = 10;
var targetSVG = null;
var scaleTimer;
function enlargeIt(svgObject) {
  clearTimeout(scaleTimer);
  scaleTimer = 0;
  if(svgObject == -1) {
    svgObject = targetSVG;
  }
  if(svgObject!=svgObject.parentNode.lastChild) { 
    // Sørg for at flytte det valgte SVG objekt øverst!
    var parentNode = svgObject.parentNode;
    parentNode.removeChild(svgObject);
    parentNode.appendChild(svgObject);
  }
  targetSVG = svgObject;
   
  targetScale = 12;
  if(targetScale!=currentScale)
  {
    currentScale++;
    svgObject.setAttribute("transform", "scale("+(currentScale/10.0)+")" );
    scaleTimer = setTimeout('enlargeIt(-1)',200);
  }
  else scaleTimer = 0;
}
function shrinkIt(svgObject) {
  clearTimeout(scaleTimer);
  scaleTimer = 0;
  if(svgObject == -1) {
    svgObject = targetSVG;
  }
  if(svgObject!=svgObject.parentNode.lastChild) {
    // Sørg for at flytte det valgte SVG objekt øverst!
    var parentNode = svgObject.parentNode;
    parentNode.removeChild(svgObject); 
    parentNode.appendChild(svgObject);
  }
  targetSVG = svgObject;
  
  targetScale = 8;
  if(targetScale!=currentScale)
  {
    currentScale--;
    svgObject.setAttribute("transform", "scale("+(currentScale/10.0)+")" );
    scaleTimer = setTimeout('shrinkIt(-1)',200);
  }
  else scaleTimer = 0;
}

Håber det var fornøjelig læsning.

mvh Michael Schøler

One Response på “Animerede SVG filer i IExplorer og Firefox”

  1. gravatar 1 Jakob Kruse
    2. maj 2006 kl. 09:55

    Nydelig artikel, og velkommen til!

Skriv en kommentar