TinyMCE display as A4 - javascript

I have a TinyMCE editor on my website and I would like to have the editable area (or the whole thing) displayed in A4 format.
Basically, I would like to view the document in the same way as in MS Word. (width, pagebreaks etc.)
Is that even possible? Please point me in the right direction.

Everybody says it's difficult, but Google already done it in Google Docs (TIP: you could use Google API and even get the PDF version of your document. I didn't do this, because we needed extra functions in the editor.)
Here's my solution:
I have resized the page to A4 width
Added a ruler, that shows how much page is left (obviously not 100% reliable, but close). And even page numbers! Yes!
Thoughts:
Ruler is much easier than trying to show each page, which would mean to split up the contents... IT WOULD BE DOPE THOUGH... I had my attempts to do full A4 pages even using css clip, but it was messing with text selection, so I don't know... I wish I could do it, but...
The reason I used SVG inside HTML tag is because it's the only thing I can place there... if you select all text in TinyMCE you could erase my ruler, or even copy and paste... even if you used contenteditable="false"... the choices were limited.
See here my solution:
https://jsfiddle.net/mzvarik/59smpdv8/
// plugin pravítko
tinymce.PluginManager.add('ruler', function(editor) {
var domHtml;
var lastPageBreaks;
function refreshRuler()
{
console.log("ddd");
try {
domHtml = $( editor.getDoc().getElementsByTagName('HTML')[0] );
// HACK - erase this, I have to put my CSS here
console.log($('tinystyle').html() );
domHtml.find('head').append( $('<style>'+$('tinystyle').html()+'</style>'));
} catch (e) {
return setTimeout(refreshRuler, 50);
}
var dpi = 96
var cm = dpi/2.54;
var a4px = cm * (29.7-5.7); // A4 height in px, -5.5 are my additional margins in my PDF print
// ruler begins (in px)
var startMargin = 4;
// max size (in px) = document size + extra to be sure, idk, the height is too small for some reason
var imgH = domHtml.height() + a4px*5;
var pageBreakHeight = 14; // height of the pagebreak line in tinyMce
var pageBreaks = [];
domHtml.find('.mce-pagebreak').each(function(){
pageBreaks[pageBreaks.length] = $(this).offset().top;
});
pageBreaks.sort();
// if pageBreak is too close next page, then ignore it
if (lastPageBreaks == pageBreaks) {
return; // no change
}
lastPageBreaks = pageBreaks;
console.log("Redraw ruler");
var s = '';
s+= '<svg width="100%" height="'+imgH+'" xmlns="http://www.w3.org/2000/svg">';
s+= '<style>';
s+= '.pageNumber{font-weight:bold;font-size:19px;font-family:verdana;text-shadow:1px 1px 1px rgba(0,0,0,.6);}';
s+= '</style>';
var pages = Math.ceil(imgH/a4px);
var i, j, curY = startMargin;
for (i=0; i<pages; i++)
{
var blockH = a4px;
var isPageBreak = 0;
for (var j=0; j<pageBreaks.length; j++) {
if (pageBreaks[j] < curY + blockH) {
// musime zmensit velikost stranky
blockH = pageBreaks[j] - curY;
// pagebreak prijde na konec stranky
isPageBreak = 1;
pageBreaks.splice(j, 1);
}
}
s+= '<line x1="0" y1="'+curY+'" x2="100%" y2="'+curY+'" stroke-width="1" stroke="red"/>';
// zacneme pravitko
s+= '<pattern id="ruler'+i+'" x="0" y="'+curY+'" width="37.79527559055118" height="37.79527559055118" patternUnits="userSpaceOnUse">';
s+= '<line x1="0" y1="0" x2="100%" y2="0" stroke-width="1" stroke="black"/>';
s+= '</pattern>';
s+= '<rect x="0" y="'+curY+'" width="100%" height="'+blockH+'" fill="url(#ruler'+i+')" />';
// napiseme cislo strany
s+= '<text x="10" y="'+(curY+19+5)+'" class="pageNumber" fill="#ffffff">'+(i+1)+'.</text>';
curY+= blockH;
if (isPageBreak) {
//s+= '<rect x="0" y="'+curY+'" width="100%" height="'+pageBreakHeight+'" fill="#FFFFFF" />';
curY+= pageBreakHeight;
}
}
s+= '</svg>';
domHtml.css('background-image', 'url("data:image/svg+xml;utf8,'+encodeURIComponent(s)+'")');
}
editor.on('NodeChange', refreshRuler);
editor.on("init", refreshRuler);
});
tinymce.init({
plugins: "ruler pagebreak",
toolbar1: "pagebreak",
selector: 'textarea',
height: 300
});
Btw.
Imagine Google would make free rich text editor!
CKEditor also can't do it and is paid, what a shame!

It is possible, but hard, error prone and you won't get near MS Word. Maybe you can get it right for one font or so.
What you need to do is a custom CSS and a custom template. The template should resemble a grey background with the white page (with a shadow :). Define some buttons that will add custom classes to the template with Javascript and you will get the margin settings (narrow, wide, normal, no values). For the page break, you can insert a special <hr> that styles the underlying page template as if it ends and another one begins. Bear in mind you will have to replace almost all of your custom CSS in order to make it print-ready. Also, you should make tinymce fullscreen.
Another (very weird) approach that I've seen is a combination between tinymce and a PDF renderer library or equivalent. This way you'll get the WYSIWYG right.
Hope that helps.

I modified the Martin's ruler. Thanks
// plugin pravítko, modified by SerhatSoylemez
tinymce.PluginManager.add("editor-ruler", function(editor) {
var domHtml;
var lastPageBreaks;
var pagen= tinymce.util.I18n.translate("p.");
function refreshRuler() {
try {
domHtml = $(editor.getDoc().getElementsByTagName('HTML')[0]);
} catch (e) {
return setTimeout(refreshRuler, 50);
}
var dpi = 96
var cm = dpi/2.54;
var a4px = cm * (29.7); // A4 height in px, -5.5 are my additional margins in my PDF print
// ruler begins (in px)
var startMargin = 0;
// max size (in px) = document size + extra to be sure, idk, the height is too small for some reason
var imgH = domHtml.height() + a4px*5;
var pageBreakHeight = 4; // height of the pagebreak line in tinyMce
var pageBreaks = []; // I changed .mce-pagebreak with .page-break !!!
domHtml.find('.page-break').each(function() {
pageBreaks[pageBreaks.length] = $(this).offset().top;
});
pageBreaks.sort();
// if pageBreak is too close next page, then ignore it
if (lastPageBreaks == pageBreaks) {
return; // no change
}
lastPageBreaks = pageBreaks;
// console.log("Redraw ruler");
var s = '';
s+= '<svg width="100%" height="'+imgH+'" xmlns="http://www.w3.org/2000/svg">';
s+= '<style>';
s+= '.pageNumber{font-weight:bold;font-size:20px;font-family:verdana;text-shadow:1px 1px 1px rgba(0,0,0,.6);}';
s+= '</style>';
var pages = Math.ceil(imgH/a4px);
var i, j, curY = startMargin;
for (i=0; i<pages; i++) {
var blockH = a4px;
var isPageBreak = 0;
for (var j=0; j<pageBreaks.length; j++) {
if (pageBreaks[j] < curY + blockH) {
// musime zmensit velikost stranky
blockH = pageBreaks[j] - curY;
// pagebreak prijde na konec stranky
isPageBreak = 1;
pageBreaks.splice(j, 1);
}
}
curY2 = curY+38;
s+= '<line x1="0" y1="'+curY2+'" x2="100%" y2="'+curY2+'" stroke-width="1" stroke="red"/>';
// zacneme pravitko
s+= '<pattern id="ruler'+i+'" x="0" y="'+curY+'" width="37.79527559055118" height="37.79527559055118" patternUnits="userSpaceOnUse">';
s+= '<line x1="0" y1="0" x2="100%" y2="0" stroke-width="1" stroke="black"/>';
s+= '<line x1="24" y1="0" x2="0" y2="100%" stroke-width="1" stroke="black"/>';
s+= '</pattern>';
s+= '<rect x="0" y="'+curY+'" width="100%" height="'+blockH+'" fill="url(#ruler'+i+')" />';
// napiseme cislo strany
s+= '<text x="10" y="'+(curY2+19+5)+'" class="pageNumber" fill="#e03e2d">'+pagen+(i+1)+'.</text>';
curY+= blockH;
if (isPageBreak) {
//s+= '<rect x="0" y="'+curY+'" width="100%" height="'+pageBreakHeight+'" fill="#ffffff" />';
curY+= pageBreakHeight;
}
}
s+= '</svg>';
domHtml.css('background-image', 'url("data:image/svg+xml;utf8,'+encodeURIComponent(s)+'")');
}
function deleteRuler() {
domHtml.css('background-image', '');
}
var toggleState = false;
editor.on("NodeChange", function () {
if (toggleState == true) {
refreshRuler();
}
});
editor.on("init", function () {
if (toggleState == true) {
refreshRuler();
}
});
editor.ui.registry.addIcon("square_foot", '<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24" viewBox="0 0 24 24" width="24">'+
'<g><rect fill="none" height="24" width="24"/></g><g><g><path d="M17.66,17.66l-1.06,1.06l-0.71-0.71l1.06-1.06l-1.94-1.94l-1.06,1.06l-0.71-0.71'+
'l1.06-1.06l-1.94-1.94l-1.06,1.06 l-0.71-0.71l1.06-1.06L9.7,9.7l-1.06,1.06l-0.71-0.71l1.06-1.06L7.05,7.05L5.99,8.11L5.28,7.4l1.06-1.06L4,4'+
'v14c0,1.1,0.9,2,2,2 h14L17.66,17.66z M7,17v-5.76L12.76,17H7z"/></g></g></svg>');
editor.ui.registry.addToggleMenuItem("ruler", {
text: "Show ruler",
icon: "square_foot",
onAction: function() {
toggleState = !toggleState;
if (toggleState == false) {
deleteRuler();
} else {
refreshRuler();
}
},
onSetup: function(api) {
api.setActive(toggleState);
return function() {};
}
});
});
function loadJavascript(url) {
var script = document.createElement("script");
script.src = url;
document.head.appendChild(script);
}
loadJavascript("https://code.jquery.com/jquery-3.5.1.min.js");

Related

Rails JavaScript code isn't executing when page is accessed with link

I'm having a strange problem... I have a page that uses some JavaScript to make an image inside of an SVG draggable and it works. But not when accessing the page with a link from another page. Reloading the page after the initial load works perfectly and even visiting the page directly via the URL bar works. I am at a loss as to what to try.
In my research, I stumbled upon this answer: Rails, javascript not loading after clicking through link_to helper
But upon trying all the solutions, none seemed to fix my problem, though it could easily be that I wasn't applying them to my code correctly. My SVG element as a 'onload' attribute that points to the 'makeDraggable(evt)' function, and most of the solutions on there made it so it couldn't access that function.
Here's the code for the initial link that I generate:
<%= link_to 'Play', canvas_path(:game => #game) %>
Here's my HTML code:
<div id="container">
<svg id="svg" onload="makeDraggable(evt)" width="50%" height="90%"
xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="grid" width="40" height="40"
patternUnits="userSpaceOnUse">
<rect width="80" height="80" fill="url(#smallGrid)"/>
<path d="M 80 0 L 0 0 0 80" fill="none" stroke="black" stroke-
width="1"/>
</pattern>
</defs>
<%#game_assets.each do |asset| %>
<image class="draggable" id="<%=asset.id %>" height="536" width="536"
xlink:href="<%= url_for(asset.image) %>" x="<%= asset.position_x %>" y="
<%= asset.position_y %>" style="position: relative;"
transform="translate(0 0)"></image>
<% end %>
<rect width="100%" height="100%" style="pointer-events: none;"
fill="url(#grid)" />
</svg>
</div>
And here is my JavaScript:
$(document).on('turbolinks:load', function () {
$('#svg').draggable();
$('.draggable').draggable();
});
// Makes content inside of SVG draggable
// Source: http://www.petercollingridge.co.uk/tutorials/svg/interactive/dragging/
function makeDraggable(evt) {
var svg = evt.target;
svg.addEventListener('mousedown', startDrag);
svg.addEventListener('mousemove', drag);
svg.addEventListener('mouseup', endDrag);
svg.addEventListener('mouseleave', endDrag);
svg.addEventListener('touchstart', startDrag);
svg.addEventListener('touchmove', drag);
svg.addEventListener('touchend', endDrag);
svg.addEventListener('touchleave', endDrag);
svg.addEventListener('touchcancel', endDrag);
var selectedElement, offset, transform,
bbox, minX, maxX, minY, maxY, confined;
var boundaryX1 = 10.5;
var boundaryX2 = 30;
var boundaryY1 = 2.2;
var boundaryY2 = 19.2;
function getMousePosition(evt) {
var CTM = svg.getScreenCTM();
if (evt.touches) { evt = evt.touches[0]; }
return {
x: (evt.clientX - CTM.e) / CTM.a,
y: (evt.clientY - CTM.f) / CTM.d
};
}
function startDrag(evt) {
if (evt.target.classList.contains('draggable')) {
selectedElement = evt.target;
offset = getMousePosition(evt);
console.log("started dragging")
// Make sure the first transform on the element is a translate transform
var transforms = selectedElement.transform.baseVal;
if (transforms.length === 0 || transforms.getItem(0).type !==
SVGTransform.SVG_TRANSFORM_TRANSLATE) {
// Create an transform that translates by (0, 0)
var translate = svg.createSVGTransform();
translate.setTranslate(0, 0);
selectedElement.transform.baseVal.insertItemBefore(translate,
0);
}
// Get initial translation
transform = transforms.getItem(0);
offset.x -= transform.matrix.e;
offset.y -= transform.matrix.f;
confined = evt.target.classList.contains('confine');
if (confined) {
bbox = selectedElement.getBBox();
minX = boundaryX1 - bbox.x;
maxX = boundaryX2 - bbox.x - bbox.width;
minY = boundaryY1 - bbox.y;
maxY = boundaryY2 - bbox.y - bbox.height;
}
}
}
function drag(evt) {
if (selectedElement) {
evt.preventDefault();
console.log("drag triggered")
var coord = getMousePosition(evt);
var dx = coord.x - offset.x;
var dy = coord.y - offset.y;
if (confined) {
if (dx < minX) { dx = minX; }
else if (dx > maxX) { dx = maxX; }
if (dy < minY) { dy = minY; }
else if (dy > maxY) { dy = maxY; }
}
transform.setTranslate(dx, dy);
}
}
function endDrag(evt) {
selectedElement = false;
}
}
If anyone could shed some light on what is happening, I would greatly appreciate it.
Also, forgive me if my formatting of this post isn't quite correct. First time poster :)
Remove onload="makeDraggable(evt)" from the #svg element, because the
onload function won't work when written inline when you're using turbolinks (as Rails does).
Then add the following to your JS:
$(document).on('turbolinks:load', function () {
if($('#svg').length == 1){
makeDraggable($('#svg'));
}
});
and change var svg = evt.target; to var svg = evt;

Measure not yet created SVG text in javascript

I'm trying to create a function that will measure how big a text element will be in a SVG element. The code examples I found at Stack Overflow does not work and gives a width of zero. If I delay the measurement I can get the text, but not right away. How is this solved?
var messureSVGtext = function(text, svg, options){
var text = document.createElementNS(svgns, 'text');
text.style.fontFamily = options.font;
text.setAttribute("style",
"font-family:" + options.font + ";" +
"font-size:" + options.fontSize + "px;"
);
var textNode = document.createTextNode(text);
text.appendChild(textNode);
svg.appendChild(text);
// This does not work
console.log(text.clientWidth);
//This does
setTimeout(function(){
console.log(text.clientWidth);
}, 100);
}
You can get the "computed style" of an element and then check the width & height from that.
Give the element an id attribute and after it is appended to the DOM, try this:
var elem1 = document.getElementById("text_elem");
var style = window.getComputedStyle(elem1, null);
console.log(style.width, style.height);
Working example
SVG
<svg
xmlns="http://www.w3.org/2000/svg"
version="1.1"
width="640"
height="480"
viewBox="0 0 640 480"
style="display:block">
<text id="svg_text" x="20" y="50" style="font-family:Arial; font-size:36px; fill:#BADA55">Hello World!</text>
</svg>
JavaScript
function getCompStyle(oid, cbf)
{
var obj, stl, itv;
obj = document.getElementById(oid);
itv = setInterval(function()
{
stl = window.getComputedStyle(obj, null);
if (stl && (stl.width.indexOf('0') != 0))
{
clearInterval(itv);
cbf(stl);
}
},0);
}
getCompStyle('svg_text', function(style)
{
console.log(style.width);
});
To use the example, place the SVG in your HTML <body> and the JavaScript in a <script> tag below the SVG - also in the <body>.

I'm trying to get this code I found for draggable SVGs to work

I found some interesting code on petercollingridge.co.uk for dragging SVGs.
After a while of trying to get it to work in my project, I decided to just try to get Peter's code to run in a fiddle.
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.1" width="400" height="200">
<style>
.draggable {
cursor: move;
}
</style>
<script type="text/ecmascript">
< ![CDATA[
var selectedElement = 0;
var currentX = 0;
var currentY = 0;
var currentMatrix = 0;
function selectElement(evt) {
selectedElement = evt.target;
currentX = evt.clientX;
currentY = evt.clientY;
currentMatrix = selectedElement.getAttributeNS(null, "transform").slice(7, -1).split(' ');
for (var i = 0; i < currentMatrix.length; i++) {
currentMatrix[i] = parseFloat(currentMatrix[i]);
}
selectedElement.setAttributeNS(null, "onmousemove", "moveElement(evt)");
selectedElement.setAttributeNS(null, "onmouseout", "deselectElement(evt)");
selectedElement.setAttributeNS(null, "onmouseup", "deselectElement(evt)");
}
function moveElement(evt) {
var dx = evt.clientX - currentX;
var dy = evt.clientY - currentY;
currentMatrix[4] += dx;
currentMatrix[5] += dy;
selectedElement.setAttributeNS(null, "transform", "matrix(" + currentMatrix.join(' ') + ")");
currentX = evt.clientX;
currentY = evt.clientY;
}
function deselectElement(evt) {
if (selectedElement != 0) {
selectedElement.removeAttributeNS(null, "onmousemove");
selectedElement.removeAttributeNS(null, "onmouseout");
selectedElement.removeAttributeNS(null, "onmouseup");
selectedElement = 0;
}
}
]] >
</script>
<rect x="0.5" y="0.5" width="399" height="199" fill="none" stroke="black" />
<rect class="draggable" x="30" y="30" width="80" height="80" fill="blue" transform="matrix(1 0 0 1 25 20)" onmousedown="selectElement(evt)" />
<rect class="draggable" x="160" y="50" width="50" height="50" fill="green" transform="matrix(1 0 0 1 103 -25)" onmousedown="selectElement(evt)" />
</svg>
I'm still getting the errors I was in my project, those being:
" Uncaught SyntaxError: Unexpected token <" and "Uncaught ReferenceError: selectElement is not defined "
I read something about invisible characters causing the first problem if you copy/paste code, but I haven't found any.
Thanks for any help you can offer.
Like others said, just remove the CDATA jumbo. Here is an updated fiddle:
https://jsfiddle.net/88pocqsr/1/
We've removed < ![CDATA[ and ]]>
Change the line
< ![CDATA[
to
<![CDATA[
(remove space between < and !)
and the line
]] >
to
]]>

Javascript - morph/animate svg path data WITHOUT SMIL or libraries

I want to morph/animate svg path data WITHOUT SMIL or libraries (jquery, Snap.svg, Velocity.js etc.), just pure javascript (and optionally with css if possible). I want to do this because Chrome deprecated SMIL and they suggest to animate with css or "Web animations" -what do they mean with web animations??-. For example I want to morph path with id "rect1" to path with id "rect2" in code below:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>morph/animate svg path data WITHOUT SMIL or libraries (jquery, Snap.svg, Velocity.js etc.), just pure javascript</title>
</head>
<body>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1"
width="500px" height="500px"
viewBox="0 0 500 500"
>
<g id="layer1">
<path id="rect1" d="m25 7.3 61 12-6 57-71 6z" stroke="#f00" stroke-miterlimit="10" stroke-width="2" fill="none"/>
</g>
<g id="layer2">
<path id="rect2" d="m9.3 34 59-27 26 41-54 42z" stroke="#f00" stroke-miterlimit="10" stroke-width="2" fill="none"/>
</g>
</svg>
<script>
// code for animating/morphing path with id "rect1" to path with id "rect 2"
</script>
</body>
</html>
To animate the path data using just JavaScript, I would suggest caching the original path data for both paths. Then use a timer to step through the animation process. At each step, calculate current path data that smoothly moves from start path data to end path data.
The following code waits 1 second and then runs a 2 second animation...
<script>
(function() {
var path1;
var path2;
var startPoints;
var endPoints;
var currentStep = 0;
var maximumSteps = 100;
var timeBeforeFirstStep = 1000;
var timeBetweenSteps = 20;
function animatePath() {
if (currentStep < maximumSteps) {
currentStep = currentStep + 1;
for (var i = 0; i < path1.pathSegList.numberOfItems; i++) {
var item = path1.pathSegList.getItem(i);
if (item.pathSegType === SVGPathSeg.PATHSEG_MOVETO_REL || item.pathSegType === SVGPathSeg.PATHSEG_LINETO_REL) {
if (startPoints[i] && endPoints[i]) {
item.x = startPoints[i].x + (endPoints[i].x - startPoints[i].x) * (currentStep / maximumSteps);
item.y = startPoints[i].y + (endPoints[i].y - startPoints[i].y) * (currentStep / maximumSteps);
}
}
}
setTimeout(animatePath, timeBetweenSteps);
}
}
function window_load() {
path1 = document.getElementById("rect1");
path2 = document.getElementById("rect2");
startPoints = [];
for (var i = 0; i < path1.pathSegList.numberOfItems; i++) {
var item = path1.pathSegList.getItem(i);
if (item.pathSegType === SVGPathSeg.PATHSEG_MOVETO_REL || item.pathSegType === SVGPathSeg.PATHSEG_LINETO_REL) {
startPoints.push({"x": item.x, "y": item.y});
}
else {
startPoints.push(null);
}
}
endPoints = [];
for (var i = 0; i < path2.pathSegList.numberOfItems; i++) {
var item = path2.pathSegList.getItem(i);
if (item.pathSegType === SVGPathSeg.PATHSEG_MOVETO_REL || item.pathSegType === SVGPathSeg.PATHSEG_LINETO_REL) {
endPoints.push({"x": item.x, "y": item.y});
}
else {
endPoints.push(null);
}
}
setTimeout(animatePath, timeBeforeFirstStep);
}
window.addEventListener("load", window_load);
}());
</script>

Svg : iframe can not delete ! Why?

I am trying to set up an animated icon in svg.
I got the code on a tutorial site ... Everything works except that when I try to delete an iframe in HTML code (the only one), the animation stops working. Why?
I puts you the codes in a zip file. If anyone can help me, it'd be great!
Edit:
HTML :
<!DOCTYPE html>
<html lang="en" style="">
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script src="js/vendor/jquery.svginject.js"></script>
<script src="js/site.js"></script>
</head>
<body style="background-color:#ffffff">
<img src="img/audio-spectrum-analyzer.svg" data-audiofile="fondspacial.mp3" width="18" class="svg-inject">
</body>
</html>
<iframe style="display:none" frameborder="0" height="0" width="0" src="nothing.html"></iframe>
SVG (image):
<svg id="audio-spectrum-analyzer" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" height="320px" width="210px" version="1.1" y="0px" x="0px" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 210 320" onclick="toggleAudio()">
<g id="eq-bars" height="320px" width="210px" fill="#010101" transform="">
<rect x="00" y="150" height="20" width="10" />
<rect x="20" y="140" height="40" width="10" />
<rect x="40" y="100" height="120" width="10" />
<rect x="60" y="120" height="80" width="10" />
<rect x="80" y="60" height="200" width="10" />
<rect x="100" y="20" height="280" width="10" />
<rect x="120" y="70" height="180" width="10" />
<rect x="140" y="120" height="80" width="10" />
<rect x="160" y="140" height="40" width="10" />
<rect x="180" y="150" height="20" width="10" />
<rect x="200" y="155" height="10" width="10" />
</g>
<defs>
<style type="text/css"><![CDATA[
svg#audio-spectrum-analyzer {
margin: 0 auto;
}
]]></style>
</defs>
<script type="application/javascript"><![CDATA[
var context;
if (typeof AudioContext !== "undefined") {
context = new AudioContext();
}
else if (typeof webkitAudioContext !== "undefined") {
context = new webkitAudioContext();
}
else {
throw new Error('AudioContext not supported. :(');
}
var eqHeight = document.querySelector('svg#audio-spectrum-analyzer > g#eq-bars').getAttribute('height').replace('px', '');
var bars = document.querySelectorAll('svg#audio-spectrum-analyzer rect');
var playing = false;
var audioFileUrl = document.querySelector('svg#audio-spectrum-analyzer').getAttribute('data-audiofile');
if (audioFileUrl === undefined) {
throw new Error('Audio File not defined');
}
var soundSource;
var fft;
var fftSmoothing = 0.6;
var fftMaxValue = 256;
var samples = 128;
var sampleIntervalID;
var ampFactor = 1.25;
var numBars = bars.length;
var soundBuffer;
var request = new XMLHttpRequest();
request.open("GET", audioFileUrl, true);
request.responseType = "arraybuffer";
// Our asynchronous callback
request.onload = function () {
var audioData = request.response;
// The Audio Context handles creating source
// buffers from raw binary data
soundBuffer = context.createBuffer(audioData, true /*make mono*/ );
};
request.send();
function sampleAudio() {
var data = new Uint8Array(fft.frequencyBinCount);
fft.getByteFrequencyData(data);
// Calc bin size to sum freqs into.
// Carve off some of the high-end, lower energy bars (+2)
var bin_size = Math.floor(data.length / (numBars + 2));
// Sum up and average the samples into their bins
for (var i = 0; i < numBars; ++i) {
// Sum this bin
var sum = 0;
for (var j = 0; j < bin_size; ++j) {
sum += data[(i * bin_size) + j];
}
// Duck some of the low-end power
if (i === 0) {
sum = sum * 0.75;
}
// Calculate the average frequency of the samples in the bin
var average = sum / bin_size;
var scaled_average = Math.max(10, ((average / fftMaxValue) * eqHeight) * ampFactor);
// Update eq bar height
bars[i].setAttribute('height', scaled_average);
// Center bar
bars[i].setAttribute('y', (eqHeight - scaled_average) / 2);
}
}
function playSound() {
// create a sound source
soundSource = context.createBufferSource();
// Add the buffered data to our object
soundSource.buffer = soundBuffer;
// Create the FFT
fft = context.createAnalyser();
fft.smoothingTimeConstant = fftSmoothing;
fft.fftSize = samples;
soundSource.connect(fft);
fft.connect(context.destination);
soundSource.noteOn(context.currentTime);
// Start the FFT sampler
sampleIntervalID = setInterval(sampleAudio, 30);
playing = true;
}
function stopSound() {
// Stop the FFT sampler
clearInterval(sampleIntervalID);
if (soundSource) {
soundSource.noteOff(context.currentTime);
}
playing = false;
}
var toggleAudio = function () {
if (!playing) {
playing = true;
playSound();
}
else {
stopSound();
playing = false;
}
}
window.addEventListener('load', function () {
window.toggleAudio = toggleAudio;
}, false);
]]></script>
</svg>
Javascript :
;(function ( $, window, document, undefined ) {
var pluginName = 'svgInject';
function Plugin(element, options) {
this.element = element;
this._name = pluginName;
this.init();
}
Plugin.prototype = {
init: function () {
$(this.element).css('visibility', 'hidden');
this.swapSVG(this.element);
},
swapSVG: function (el) {
var imgURL = $(el).attr('src');
var imgID = $(el).attr('id');
var imgClass = $(el).attr('class');
var imgData = $(el).clone(true).data();
var dimensions = {
w: $(el).attr('width'),
h: $(el).attr('height')
};
$.get(imgURL, function (data) {
var svg = $(data).find('svg');
if (typeof imgID !== undefined) {
svg = svg.attr('id', imgID);
}
if (typeof imgClass !== undefined) {
var cls = (svg.attr('class') !== undefined) ? svg.attr('class') : '';
svg = svg.attr('class', imgClass + ' ' + cls + ' replaced-svg');
}
$.each(imgData, function (name, value) {
svg[0].setAttribute('data-' + name, value);
});
svg = svg.removeAttr('xmlns:a');
var ow = parseFloat(svg.attr('width'));
var oh = parseFloat(svg.attr('height'));
if (dimensions.w && dimensions.h) {
$(svg).attr('width', dimensions.w);
$(svg).attr('height', dimensions.h);
}
//Scale proportionally based on width
else if (dimensions.w) {
$(svg).attr('width', dimensions.w);
$(svg).attr('height', (oh / ow) * dimensions.w);
}
//Scale proportionally based on height
else if (dimensions.h) {
$(svg).attr('height', dimensions.h);
$(svg).attr('width', (ow / oh) * dimensions.h);
}
$(el).replaceWith(svg);
var js = new Function(svg.find('script').text());
js();
});
}
};
$.fn[pluginName] = function (options) {
return this.each(function () {
if (!$.data(this, 'plugin_' + pluginName)) {
$.data(this, 'plugin_' + pluginName, new Plugin(this, options));
}
});
};
})(jQuery, window, document);
Edit site.js to inject SVG on DOM ready.
$(document).ready(function() {
$('.svg-inject').svgInject();
});
NOTE: script in SVG file uses deprecated web audio API.
createBuffer() used to be able to take compressed data and give back decoded samples, but this ability was removed from the spec, because all the decoding was done on the main thread, therefore createBuffer() was blocking other code execution.
Source: MDN

Categories