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.
alertIdfunktionen 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 kalderalertIdfunktionen:
<?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 kalderalertIdfunktionen:
<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
2. maj 2006 kl. 09:55
Nydelig artikel, og velkommen til!