I'm programming a firefox addon and using a panel to display info on a video, everything works fine althought I can't make the panel transparent. I define the panel styling in the html file as follow:
<html>
<head>
<meta charset="utf-8" />
<style type="text/css" media="all">
html
{
opacity:0.1;
border-style:none;
resize:none;
}
textarea
{
background-color:transparent;
resize: none;
border-style:none;
}
</style>
</head>
<body>
<textarea id="text" readonly=true rows="3" cols="60"></textarea>
</panel>
</body>
</html>
Except the panel is not transparent only the text area is. I tried with:
opacity:1 for textarea
It doesn't work either way. What am I doing wrong? Is this even possible?
From what I understand :
html
{
opacity:0.1;
border-style:none;
resize:none;
}
only applies to the panel content not to the panel itself. I found a post on this subject but it is outdated since the sdk/panel.js mentionned in the post is not the same anymore.
Anyway I tried downloading the panel.js and replacing the current one, but it doesn't seem to affect the panel I display at all. The panel is still white and the border-radius option does not work either. (I should say that I replaced all the "./" with "sdk/" as mentionned in that post).
Ok here is a pure addon sdk solution:
let myPanel = Panel({
width: 180,
height: 180,
contentURL: 'data:text/html,<textarea style="width:120px; height:80px;">this is my textarea</textarea>'
})
let { getActiveView }=require("sdk/view/core");
getActiveView(myPanel).setAttribute("noautohide", true);
getActiveView(myPanel).setAttribute("level", 'top');
getActiveView(myPanel).setAttribute("style", 'background-color:rgba(0, 0, 0, 0.2);');
You can't style the panel provided in the SDK, only the content but you can definitely follow the procedure you mention and provide your modified panel.
I had to solve this same problem today (transparent panel in SDK). The trick is getting at the anonymous content:
function makePanelTransparent() {
// Get the panel element in the XUL DOM and make its background transparent.
const { getActiveView } = require('sdk/view/core');
const el = getActiveView(panel);
el.style.background = 'rgba(0,0,0,0)';
// Go up the XUL DOM till you hit the Document (nodeType 9).
let parentNode = el;
while (parentNode !== null && parentNode.nodeType !== 9) {
parentNode = parentNode.parentNode;
}
if (!parentNode) {
console.error('unable to find the document parent; giving up');
return;
}
// Now that we've found it, call the document a document.
const xulDocument = parentNode;
// Use the document pointer to access and style 'anonymous' content.
const xulContainer = xulDocument.getAnonymousElementByAttribute(el, 'class', 'panel-arrowcontent')
xulContainer.style.background = 'rgba(0,0,0,0)';
xulContainer.style.boxShadow = 'none';
}
This works for me. Hope it helps some other person in the next 1-5 years ;-)
I found out that you could create a panel with transparency this way:
var win = Services.wm.getMostRecentWindow('navigator:browser');
var panel = win.document.createElement('panel');
var screen = Services.appShell.hiddenDOMWindow.screen;
var props = {
noautohide: true,
noautofocus: false,
level: 'top',
style: 'padding:15px; margin:0; width:' + screen.width + 'px; height:' + screen.height + 'px; background-color:rgba(180,180,180,.5);'
}
for (var p in props) {
panel.setAttribute(p, props[p]);
}
win.document.querySelector('#mainPopupSet').appendChild(panel);
panel.addEventListener('dblclick', function () {
panel.parentNode.removeChild(panel)
}, false);
panel.openPopup(null, 'overlap', screen.availLeft, screen.availTop);
To embed an iframe remember to set the path to your ".html" as:
"resource://"id of your addon"-at-jetpack/data/custom_panel.html".
Here is my code :
var win = Services.wm.getMostRecentWindow('navigator:browser');
var panel = win.document.createElement('panel');
var screen = Services.appShell.hiddenDOMWindow.screen;
var props = {
noautohide: true,
noautofocus: false,
backdrag: true,
level: 'top',
style: 'padding:10px; margin:0; width:530px; height:90px; background-color:rgba(180,180,180,.5);'
}
for (var p in props) {
panel.setAttribute(p, props[p]);
}
var iframe = win.document.createElement('iframe');
iframe.setAttribute('src','resource://"id of your addon"-at-jetpack/data/custom_panel.html');
panel.appendChild(iframe);
win.document.querySelector('#mainPopupSet').appendChild(panel);
panel.addEventListener('dblclick', function () {
panel.parentNode.removeChild(panel)
}, false);
panel.openPopup(null, 'overlap', screen.availLeft+screen.width/2-256, screen.availTop+760);
Thanks Noitidart for the help.
Related
I currently have a page on my site that when you go to, it forwards you to a random page on my site. Right now I am trying to shrink that list if the user is viewing it on mobile. I am having trouble figure this out. Right now I have made two different onLoad functions in the script section, and media queries that remove the html that loads it based on screen size.
<html>
<head>
<script type="text/javascript">
<!--
// Create an array of the links to choose from:
var links = new Array();
links[0] = "spinguy.html";
links[1] = "hardware.html";
links[2] = "flappymatt.html";
links[3] = "spinzone.html";
links[4] = "shlink.html";
links[5] = "goop.html";
links[6] = "spinzone.html";
links[7] = "index1.html";
links[8] = "ghoul.html";
links[9] = "grandma.html";
function openLink() {
// Chooses a random link:
var i = Math.floor(Math.random() * links.length);
// Directs the browser to the chosen target:
parent.location = links[i];
return false;
}
<!--
// Create an array of the links to choose from:
var links = new Array();
links[0] = "spinguy.html";
links[1] = "hardware.html";
links[2] = "flappymatt.html";
links[3] = "shlink.html";
links[4] = "ghoul.html";
links[5] = "grandma.html";
function openPhoneLink() {
// Chooses a random link:
var i = Math.floor(Math.random() * links.length);
// Directs the browser to the chosen target:
parent.location = links[i];
return false;
}
//-->
</script>
<style>
.phone{
display: none;
}
#media only screen and (max-width: 800px) {
.computer{
display: none;
}
.phone{
display: inline;
}
}
#media only screen and (max-width: 400px) {
.computer{
display: none;
}
.phone{
display: inline;
}
}
</style>
</head>
<body>
<div class="computer" onload="openLink();"></div>
<div class="phone" onload="openPhoneLink();"></div>
</body>
</html>
A few things.
divs don't have an onload event -- this honor is reserved for the body. As such, I moved your onload call to the body tag. <body onload="openLink()">. Check this out.
display: none; doesn't keep an element from loading, it just keeps it from appearing on the page.
You don't even need to use divs for this. If you look at how I have re-written what you are trying to do, you'll see that there aren't any divs on the page. I simply call the function onload and replace the location within the openLink() function.
Use window.location.href to change a page's URL. StackOverflow question.
You don't need to return false at the end of JavaScript functions. They just return to the program flow when they end. (But if you did call them from something that expected a return value and did not have one, you would get undefined.)
I didn't update openLink() to choose from a different array based on the size, but you should use something like let windowWidth = window.innerHeight; and use an if statement depending on the value. Also, take a look at some better ways to use and populate arrays in JavaScript.
<head>
<script type="text/javascript">
//get size of window using window.innerWidth and make the link array according to the value
// Create an array of the links to choose from:
function openLink() {
var links = new Array();
links[0] = "spinguy.html";
links[1] = "hardware.html";
links[2] = "flappymatt.html";
links[3] = "spinzone.html";
links[4] = "shlink.html";
links[5] = "goop.html";
links[6] = "spinzone.html";
links[7] = "index1.html";
links[8] = "ghoul.html";
links[9] = "grandma.html";
// Chooses a random link:
var i = Math.floor(Math.random() * links.length);
// Directs the browser to the chosen target:
window.location.href = links[i];
}
</script>
</head>
<body onload="openLink()"></body>
</html>
First, your onload attributes will not work because it is only a valid attribute on a subset of HTML elements, namely:
<body>, <iframe>, <img>, <input>, <link>, <script>, <style>
This is because those elements have some sort of async loading associated with them. Check this w3schools.com page for reference.
I assume based on your description that you intended these onload events to occur on the <body> because it has the distinct behavior "to execute a script once a web page has completely loaded all content".
This is okay, because you don't need to separate functions for openLink() and openPhoneLink(), and these can be collapsed into one function for simplicity.
You can avoid the wonkiness of the #media CSS queries as Javascript has the ability to determine the width and height of your viewport anyway.
window.screen.width - device width
window.screen.height - device height
If you want to get real fancy with dimensions and device rules, you can find references like this one which give you a breakdown of what different dimensional variables in Javascript mean.
Now, back to the question at hand...
You can test the device switch by using something like the Chrome Developer Tools and toggling between mobile devices and refreshing the browser. In this case, my 800 pixel check is not good enough to know an iPad is not a computer, but you can put whatever rules you want in here.
I've taken the liberty to give you a more re-usable data structure for your links. I hope this helps you out:
<html>
<head>
<script type="text/javascript">
const Device = {
COMPUTER: "computer",
PHONE: "phone"
}
const links = [
{ href: "spinguy.html", computer: true, phone: true, },
{ href: "hardware.html", computer: true, phone: true, },
{ href: "flappymatt.html", computer: true, phone: true, },
{ href: "spinzone.html", computer: true, phone: false, },
{ href: "shlink.html", computer: true, phone: true, },
{ href: "goop.html", computer: true, phone: false, },
{ href: "index1.html", computer: true, phone: false, },
{ href: "ghoul.html", computer: true, phone: true, },
{ href: "grandma.html", computer: true, phone: true, },
]
const randomIndex = (length) => {
return Math.floor(Math.random() * length)
}
const device = () => {
// modify as needed to distinguish devices you care about
return window.screen.width >= 800 ? Device.COMPUTER : Device.PHONE
}
const openRandomLink = () => {
const deviceKey = device()
// console.log("deviceKey", deviceKey)
const validLinks = links.filter(link => link[deviceKey])
const randomLinkIndex = randomIndex(validLinks.length)
const randomLink = validLinks[randomLinkIndex].href
// console.log("randomLink", randomLink)
window.location.href = randomLink
}
</script>
</head>
<body onload="openRandomLink()">
</body>
</html>
Good day. I am developing a Web Application and there's a part where I print the form on button click. To achieve this, I overrode the definition of my Form Panel so that I can call form.print() anywhere in my code when I need to. Here is how I overrode my form:
Ext.define('my_app_name.override.form.Panel', {
override: 'Ext.form.Panel',
print: function(pnl) {
if (!pnl) {
pnl = this;
}
// instantiate hidden iframe
var iFrameId = "printerFrame";
var printFrame = Ext.get(iFrameId);
if (printFrame === null) {
printFrame = Ext.getBody().appendChild({
id: iFrameId,
tag: 'iframe',
cls: 'x-hidden',
style: {
display: "none"
}
});
}
var cw = printFrame.dom.contentWindow;
// instantiate application stylesheets in the hidden iframe
var stylesheets = "";
for (var i = 0; i < document.styleSheets.length; i++) {
stylesheets += Ext.String.format('<link rel="stylesheet" href="{0}" />', document.styleSheets[i].href);
}
// various style overrides
stylesheets += ''.concat(
"<style>",
".x-panel-body {overflow: visible !important;}",
// experimental - page break after embedded panels
// .x-panel {page-break-after: always; margin-top: 10px}",
"</style>"
);
// get the contents of the panel and remove hardcoded overflow properties
var markup = pnl.getEl().dom.innerHTML;
while (markup.indexOf('overflow: auto;') >= 0) {
markup = markup.replace('overflow: auto;', '');
}
var str = Ext.String.format('<html><head>{0}</head><body>{1}</body></html>',stylesheets,markup);
// output to the iframe
cw.document.open();
cw.document.write(str);
cw.document.close();
// remove style attrib that has hardcoded height property
cw.document.getElementsByTagName('DIV')[0].removeAttribute('style');
// print the iframe
cw.print();
// destroy the iframe
Ext.fly(iFrameId).destroy();
}
});
Then on a click of a button in my Web App, I do something like:
var form = Ext.getCmp('formIDHere');
form.print();
However, this code is rather inconsistent at times. There are times that I can print the form no problem and there are times that it gives the "Print Preview Error" message. I can't replicate the issue consistently and the logs aren't showing anything so I'm in the dark.
What I've noticed however, is that when I save my project (I'm using Sencha Architect), preview it (or refresh the current window where I'm previewing my Web App), stay with the web app all throughout the process (meaning I don't shift tabs or windows), hit the print button, the print preview appears and I don't have problems with it.
So far I haven't tested in other Web Browsers. Any ideas anyone? I'll be really thankful for anyone who can point out what I'm doing wrong. Thanks in advance.
Sorry I forgot to update this. Thanks to whoever upvoted my question.
The concept is simple. Since ExtJS4 is asynchronous, I placed my code in "blocks" and then I delayed my calls to those functions to ensure that they finish constructing what they need to construct before moving on to the next part.
print: function(pnl) {
if (!pnl) {
pnl = this;
}
// instantiate hidden iframe
var iFrameId = "printerFrame";
var printFrame = Ext.get(iFrameId);
if (printFrame === null) {
printFrame = Ext.getBody().appendChild({
id: iFrameId,
tag: 'iframe',
cls: 'x-hidden',
style: {
display: "none"
}
});
}
var cw = printFrame.dom.contentWindow;
var stylesheets = "";
var markup;
// instantiate application stylesheets in the hidden iframe
var printTask = new Ext.util.DelayedTask(function(){
// print the iframe
cw.print();
// destroy the iframe
Ext.fly(iFrameId).destroy();
});
var strTask = new Ext.util.DelayedTask(function(){
var str = Ext.String.format('<html><head>{0}</head><body>{1}</body></html>',stylesheets,markup);
// output to the iframe
cw.document.open();
cw.document.write(str);
cw.document.close();
// remove style attrib that has hardcoded height property
// cw.document.getElementsByTagName('DIV')[0].removeAttribute('style');
printTask.delay(500);
});
var markUpTask = new Ext.util.DelayedTask(function(){
// get the contents of the panel and remove hardcoded overflow properties
markup = pnl.getEl().dom.innerHTML;
while (markup.indexOf('overflow: auto;') >= 0) {
markup = markup.replace('overflow: auto;', '');
}
while (markup.indexOf('background: rgb(255, 192, 203) !important;') >= 0) {
markup = markup.replace('background: rgb(255, 192, 203) !important;', 'background: pink !important;');
}
strTask.delay(500);
});
var styleSheetConcatTask = new Ext.util.DelayedTask(function(){
// various style overrides
stylesheets += ''.concat(
"<style>",
".x-panel-body {overflow: visible !important;}",
// experimental - page break after embedded panels
// .x-panel {page-break-after: always; margin-top: 10px}",
"</style>"
);
markUpTask.delay(500);
});
var styleSheetCreateTask = new Ext.util.DelayedTask(function(){
for (var i = 0; i < document.styleSheets.length; i++) {
stylesheets += Ext.String.format('<link rel="stylesheet" href="{0}" />', document.styleSheets[i].href);
}
styleSheetConcatTask.delay(500);
});
styleSheetCreateTask.delay(500);
}
I need to display html elements as contents of a popup panel using javascript in my firefox addon.
Displaying popup using SDK is what I'm looking for but I don't want to use SDK.
panel:
<popupset id="mainPopupSet">
<panel id="htmlPanel" type="arrow">
i want to use html elements like p,div,span, etc here.
</panel>
</popupset>
javascript to open panel:
document.getElementById('htmlPanel').innerHTML = 'my custom contents';
document.getElementById('htmlPanel').openPopup(null, "before_start", 0, 0, false, false);
it seems some elements are allowed but with different behavior! i also need to set CSS for elements inside panel.
I figure it out how to do it using an iframe
changed the XUL as follow:
<popupset id="mainPopupSet">
<panel id="htmlPanel" type="arrow">
<html:iframe id="htmlContainer"/>
</panel>
</popupset>
and create a javascript function to set html contents:
function setupPanel(contents, width, height)
{
var iframe = document.getElementById("htmlContainer");
iframe.setAttribute("src","data:text/html;charset=utf-8," + escape(contents));
iframe.width = width || 300; //default width=300
iframe.height = height || 300; //default height=300
}
and usage:
setupPanel("<p>this is raw HTML.</p>");
document.getElementById('htmlPanel').openPopup(null, "before_start", 0, 0, false, false);
thanks for your hints.
With animation can copy paste to scratchpad to run it.
var win = Services.wm.getMostRecentWindow('navigator:browser');
var panel = win.document.createElement('panel');
var props = {
type: 'arrow',
style: 'width:300px;height:100px;'
}
for (var p in props) {
panel.setAttribute(p, props[p]);
}
win.document.querySelector('#mainPopupSet').appendChild(panel);
panel.addEventListener('popuphiding', function (e) {
e.preventDefault();
e.stopPropagation();
//panel.removeEventListener('popuphiding', arguments.callee, false); //if dont have this then cant do hidepopup after animation as hiding will be prevented
panel.addEventListener('transitionend', function () {
//panel.hidePopup(); //just hide it, if want this then comment out line 19 also uncomment line 16
panel.parentNode.removeChild(panel); //remove it from dom //if want this then comment out line 18
}, false);
panel.ownerDocument.getAnonymousNodes(panel)[0].setAttribute('style', 'transform:translate(0,-50px);opacity:0.9;transition: transform 0.2s ease-in, opacity 0.15s ease-in');
}, false);
panel.openPopup(null, 'overlap', 100, 100);
to display html in it, do createElementNS('html namespace i cant recall right now','iframe') then set the src of this to the html you want it to display
the type:'arrow' here is important
Since this appears to be an overlay of browser.xul, if your panel will display static content or a simple template, you can take advantage of the fact that the XHTML namespace is already declared.
<popupset id="mainPopupSet">
<panel id="htmlPanel" type="arrow">
<html:div id="htmlplaceholder">
<html:p>Lorem ipsum</html:p>
<html:p>foo <html:strong>bar</html:strong></html:p>
</html:div>
</panel>
</popupset>
The Add-on SDK hosts the html content inside an iframe, perhaps you should consider this for more complex cases.
I'm having a problem getting my code to work when it's incororated into a live site. The fiddle works just fine, but when I include the identical code in a webpage, I can't get the function to load/work at all.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
<title>Drag and Drop Assignment</title>
<!doctype html>
<link rel="stylesheet" href="styles/style1.css"/>
<style>
.drop{
float: left;
width: 350px;
height: 400px;
border: 3px solid blue;
background-image: url("http://debsiepalmer.com/images/tardis 2.jpg");
background-repeat: no-repeat;
background-size: cover;
}
#right{float: left;
width: 350px;
height: 400px;
border: 3px solid red;
}
</style>
<script src="draganddrop.js" ></script>
<script src="http://code.jquery.com/jquery-2.0.2.js" ></script>
<script src="http://code.jquery.com/ui/1.10.3/jquery-ui.js" ></script>
<script type="text/javascript">
$ (init);
function image(id, image1) {
this.id = id;
this.image1 = image1;
}
$('#deal').click(function () {dealAll(
dealCard(randomCard()));
});
$(function() {
$( "#draggable" ).draggable({ containment: "#left"});
});
function init() {
$('.drop').droppable( {
drop: handleDropEvent
} );
$("img").draggable();
}
// global variables
var cardsInDeck = new Array();
var numberOfCardsInDeck = 15;
cardsInDeck[0] = "Ace";
cardsInDeck[1] = "Grace";
cardsInDeck[2] = "Susan";
cardsInDeck[3] = "Ian";
cardsInDeck[4] = "Barbara";
cardsInDeck[5] = "Brigadier";
cardsInDeck[6] = "Romana I";
cardsInDeck[7] = "K9";
cardsInDeck[8] = "Tegan";
cardsInDeck[9] = "Jamie";
cardsInDeck[10] = "Sarah Jane";
cardsInDeck[11] = "Jo";
cardsInDeck[12] = "Romana II";
cardsInDeck[13] = "Yates";
cardsInDeck[14] = "Leela";
var cardsDealt = new Array();
function dealAll(){
var z=0;
for (z=0;z<5;z++) {
cardsDealt[z] = new Image(z,dealCard(randomCard()));
}
}
function dealCard(i) {
if (numberOfCardsInDeck == 0) return false;
var $img = new Image();
$img.src = "images/Companions/" + cardsInDeck[i] + ".jpg";
// Here I set the ID of the object
$img.id=cardsInDeck[i];
$img.class='drag';
document.body.appendChild($img);
$('#'+$img.id).draggable();
removeCard(i);
return $img;
}
// deal randomly - works
function randomCard() {
return Math.floor(Math.random() * numberOfCardsInDeck);
}
function removeCard(c)
{
for (j=c; j <= numberOfCardsInDeck - 2; j++)
{
cardsInDeck[j] = cardsInDeck[j+1];
}
numberOfCardsInDeck--;
numberOfCardsInDeck--;
numberOfCardsInDeck--;
}
function handleDropEvent( event, ui ) {
alert("Fantastic! You chose " + ui.draggable.attr("id") + " to be your companion.");
// Here I want the id of the dropped object
}
</script>
</head>
<body>
<div id="container" div style="width:750px; margin:0 auto;">
<div id="page_content" style="left: 0px; top: 0px; width: 750px" class="auto-style8">
<!--Begin Assignment 10 --->
<div id="left" class="drop">
<img id="tardis" ></img>
</div>
<input type="button" value="Get Companions" id="deal" />
<div id="content" style="left: 0px; top: 0px; width: 750px">
</div>
</div>
</div>
</body>
</html>
It's supposed to generate 5 images, one of which can be selected to be dropped onto the target and generate an alert with the id of the image being dropped. Like I said, it works just fine in the fiddle - and the code is identical on the web page, so I don't understand what I'm doing wrong.
fiddle: http://jsfiddle.net/reaglin/FUvT8/6/
I ordered some code...and for me it worked
// global variables
var cardsInDeck = [],
numberOfCardsInDeck = 5;
cardsInDeck[0] = "Ace";
cardsInDeck[1] = "Grace";
cardsInDeck[2] = "Susan";
cardsInDeck[3] = "Ian";
cardsInDeck[4] = "Barbara";
cardsInDeck[5] = "Brigadier";
cardsInDeck[6] = "Romana I";
cardsInDeck[7] = "K9";
cardsInDeck[8] = "Tegan";
cardsInDeck[9] = "Jamie";
cardsInDeck[10] = "Sarah Jane";
cardsInDeck[11] = "Jo";
cardsInDeck[12] = "Romana II";
cardsInDeck[13] = "Yates";
cardsInDeck[14] = "Leela";
//load "init" when document it's ready
$(document).on('ready',init);
function init() {
$( "#draggable" ).draggable({ containment: "#left"});
$('.drop').droppable( {drop: handleDropEvent});
}
$('#deal').click(function () {
dealAll();
});
$('#reset-pictures').click(function(){
$('img.drag').remove();
numberOfCardsInDeck = 5;
});
// deal 5 cards at once - works
function dealAll(){
// 5 cards max, no repeat cards
while(numberOfCardsInDeck){
var rand = randomCard();
dealCard(rand);
}
}
//deal cards - works
function dealCard(i) {
//create id, remove space id
var id_picture = (cardsInDeck[i] +'-'+i).replace(/\s/g, '');
//validate not exist image
if (!!$('img#'+id_picture).length) {
return;
}
var $img = $('<img/>', { src : "http://debsiepalmer.com/images/companions/" + cardsInDeck[i] + ".jpg", id : id_picture, class : 'drag', 'data-info': cardsInDeck[i]
})
$('body').append($img);
$('img.drag').draggable();
numberOfCardsInDeck--;
}
// deal randomly - works
function randomCard() {
return Math.floor(Math.random() * cardsInDeck.length);
}
// this is what to do when card drops in tardis
function handleDropEvent( event, ui ) {
alert(ui.draggable.attr("data-info"));
}
DEMO
JSFinddle
Are you linking to JQuery correctly in the live version? Sometimes when moving from a development area to a live system the references mess up, you can find out if a reference is working or not by viewing the source code and entering in the link into the URL.
This is how you fix it: Take all the javascript (from $ (init) to the alert()) and place it at the bottom of the loaded page, inside the body, after all the elements. This ensures that the javascript knows what named elements you are talking about.
I would like to emphasise that this is not a tip for writing good javascript. The problem arises because it's bad javascript to begin with. Well written pages do not rely on the position of the code within the page, rather the opposite in fact.
Postscript: I literally did the following: I came here after googling "drag and drop"; I didn't fully read the actual question; I went to the jfiddle; I copied all the code for my own purposes; I couldn't get it to work; I fixed it (just trial and error really); I fixed the bug which is still present even on the jfiddle page; and then I came back here to find out what the original query had been!
The other bug is the fact that you can't drag the images of "Romana I", "Romana II" and "Sarah Jane". As the code makes the array values the id of the image elements, maybe others can instantly spot what causes that problem.
I'm using Font-Awesome, but while the font files are not loaded, the icons appear with .
So, I want these icons to have display:none while files are not loaded.
#font-face {
font-family: "FontAwesome";
src: url('../font/fontawesome-webfont.eot');
src: url('../font/fontawesome-webfont.eot?#iefix') format('eot'), url('../font/fontawesome-webfont.woff') format('woff'), url('../font/fontawesome-webfont.ttf') format('truetype'), url('../font/fontawesome-webfont.svg#FontAwesome') format('svg');
font-weight: normal;
font-style: normal;
}
How do I know that these files have been loaded and I'm finally able to show the icons?
Edit:
I'm not talking when the page is loaded (onload), because the font could be loaded before the whole page.
Now on GitHub: https://github.com/patrickmarabeas/jQuery-FontSpy.js
Essentially the method works by comparing the width of a string in two different fonts. We are using Comic Sans as the font to test against, because it is the most different of the web safe fonts and hopefully different enough to any custom font you will be using. Additionally we are using a very large font-size so even small differences will be apparent. When the width of the Comic Sans string has been calculated, the font-family is changed to your custom font, with a fallback to Comic Sans. When checked, if the string element width is the same, the fallback font of Comic Sans is still in use. If not, your font should be operational.
I rewrote the method of font load detection into a jQuery plugin designed to give the developer the ability to style elements based upon whether the font has been loaded or not. A fail safe timer has been added so the user isn’t left without content if the custom font fails to load. That’s just bad usability.
I have also added greater control over what happens during font loading and on fail with the inclusion of classes addition and removal. You can now do whatever you like to the font. I would only recommend modifying the fonts size, line spacing, etc to get your fall back font as close to the custom as possible so your layout stays intact, and users get an expected experience.
Here's a demo: http://patrickmarabeas.github.io/jQuery-FontSpy.js
Throw the following into a .js file and reference it.
(function($) {
$.fontSpy = function( element, conf ) {
var $element = $(element);
var defaults = {
font: $element.css("font-family"),
onLoad: '',
onFail: '',
testFont: 'Comic Sans MS',
testString: 'QW#HhsXJ',
delay: 50,
timeOut: 2500
};
var config = $.extend( defaults, conf );
var tester = document.createElement('span');
tester.style.position = 'absolute';
tester.style.top = '-9999px';
tester.style.left = '-9999px';
tester.style.visibility = 'hidden';
tester.style.fontFamily = config.testFont;
tester.style.fontSize = '250px';
tester.innerHTML = config.testString;
document.body.appendChild(tester);
var fallbackFontWidth = tester.offsetWidth;
tester.style.fontFamily = config.font + ',' + config.testFont;
function checkFont() {
var loadedFontWidth = tester.offsetWidth;
if (fallbackFontWidth === loadedFontWidth){
if(config.timeOut < 0) {
$element.removeClass(config.onLoad);
$element.addClass(config.onFail);
console.log('failure');
}
else {
$element.addClass(config.onLoad);
setTimeout(checkFont, config.delay);
config.timeOut = config.timeOut - config.delay;
}
}
else {
$element.removeClass(config.onLoad);
}
}
checkFont();
};
$.fn.fontSpy = function(config) {
return this.each(function() {
if (undefined == $(this).data('fontSpy')) {
var plugin = new $.fontSpy(this, config);
$(this).data('fontSpy', plugin);
}
});
};
})(jQuery);
Apply it to your project
.bannerTextChecked {
font-family: "Lobster";
/* don't specify fallback font here, do this in onFail class */
}
$(document).ready(function() {
$('.bannerTextChecked').fontSpy({
onLoad: 'hideMe',
onFail: 'fontFail anotherClass'
});
});
Remove that FOUC!
.hideMe {
visibility: hidden !important;
}
.fontFail {
visibility: visible !important;
/* fall back font */
/* necessary styling so fallback font doesn't break your layout */
}
EDIT: FontAwesome compatibility removed as it didn't work properly and ran into issues with different versions. A hacky fix can be found here: https://github.com/patrickmarabeas/jQuery-FontFaceSpy.js/issues/1
Try WebFont Loader (github repo), developed by Google and Typekit.
This example first displays the text in the default serif font; then after the fonts have loaded it displays the text in the specified font. (This code reproduces Firefox's default behavior in all other modern browsers.)
Actually, there is a good way to understand all fonts begin to download or loaded completely or not and fall into some errors, but it is not just for a specific font, pay attention to the following code:
document.fonts.onloading = () => {
// do someting when fonts begin to download
};
document.fonts.onloadingdone = () => {
// do someting when fonts are loaded completely
};
document.fonts.onloading = () => {
// do someting when fonts fall into some error
};
And also there is an option that returns Promise and it could handle with .then function:
document.fonts.ready
.then(() => console.log('do someting at the final with each status'))
Here is a different approach to the solutions from others.
I'm using FontAwesome 4.1.0 to build WebGL textures. That gave me the idea to use a tiny canvas to render a fa-square to, then check a pixel in that canvas to test whether it has loaded:
function waitForFontAwesome( callback ) {
var retries = 5;
var checkReady = function() {
var canvas, context;
retries -= 1;
canvas = document.createElement('canvas');
canvas.width = 20;
canvas.height = 20;
context = canvas.getContext('2d');
context.fillStyle = 'rgba(0,0,0,1.0)';
context.fillRect( 0, 0, 20, 20 );
context.font = '16pt FontAwesome';
context.textAlign = 'center';
context.fillStyle = 'rgba(255,255,255,1.0)';
context.fillText( '\uf0c8', 10, 18 );
var data = context.getImageData( 2, 10, 1, 1 ).data;
if ( data[0] !== 255 && data[1] !== 255 && data[2] !== 255 ) {
console.log( "FontAwesome is not yet available, retrying ..." );
if ( retries > 0 ) {
setTimeout( checkReady, 200 );
}
} else {
console.log( "FontAwesome is loaded" );
if ( typeof callback === 'function' ) {
callback();
}
}
}
checkReady();
};
As it uses a canvas it requires a fairly modern browser, but it might work on IE8 as well with the polyfill.
Here's another way of knowing if a #font-face has already been loaded without having to use timers at all: utilize a "scroll" event to receive an instantaneous event when the size of a carefully crafted element is changed.
I wrote a blog post about how it's done and have published the library on Github.
Try something like
$(window).bind("load", function() {
$('#text').addClass('shown');
});
and then do
#text {visibility: hidden;}
#text.shown {visibility: visible;}
The load event should fire after the fonts are loaded.
alternatively, you could add font-display: block to your #font-face declaration.
this instructs browsers to render the fallback font as invisible until your font is loaded, no need for display: none or any javascript load font detection
Solution for Typescript, Angular.
If you are working with Angular, you can use this module in order to do a font check.
// document.fonts.check extension
import type {} from 'css-font-loading-module';
ngOnInit() {
this.onFontLoad();
}
public onFontLoad() {
let myTimer = setInterval(() => {
if (document.fonts.check('14px MyFont')) {
console.log('Font is loaded!');
clearInterval(myTimer);
} else {
console.log('Font is loading');
}
}, 1);
}
Also, some fonts are extremely heavy. Therefore, you can add a loading screen while the font is loading and remove the loading screen when the font is loaded. I believe this is a better approach rather than changing your CSS class to display: none, merely because it might take 3-4+ seconds to download some fonts if the user has slow internet.
This is an alternate approach that will at least ensure that font-awesome is loaded, NOT a complete solution to the OP. Original code found in the wordpress forums here https://wordpress.stackexchange.com/a/165358/40636.
It's agnostic and will work with any font style resource like font-awesome where a font-family can be checked. With a little more thought I bet this could be applied to much more...
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">
<script>
(function($){
var faSpan = $('<span class="fa" style="display:none"></span>').appendTo('body');
if (faSpan .css('fontFamily') !== 'FontAwesome' ) {
// Fallback Link
$('head').append('<link href="/css/font-awesome.min.css" rel="stylesheet">');
}
faSpan.remove();
})(jQuery);
</script>
Use the below code:
<!DOCTYPE HTML>
<html>
<head>
</head>
<body>
<canvas id="canvasFont" width="40px" height="40px" style="position: absolute; display: none;"></canvas>
<script>
function IsLoadedFonts()
{
var Args = arguments;
var obj = document.getElementById('canvasFont');
var ctx = obj.getContext("2d");
var baseFont = (/chrome/i.test(navigator.userAgent))?'tims new roman':'arial';
//................
function getImg(fon)
{
ctx.clearRect(0, 0, (obj).width, (obj).height);
ctx.fillStyle = 'rgba(0,0,0,1.0)';
ctx.fillRect( 0, 0, 40, 40 );
ctx.font = '20px '+ fon;
ctx.textBaseline = "top";
ctx.fillStyle = 'rgba(255,255,255,1.0)';
ctx.fillText( '\u0630', 18, 5 );
return ctx.getImageData( 0, 0, 40, 40 );
};
//..............
for(var i1=0; i1<Args.length; i1++)
{
data1 = getImg(Args[i1]);
data2 = getImg(baseFont);
var isLoaded = false;
//...........
for (var i=0; i<data1.data.length; i++)
{
if(data1.data[i] != data2.data[i])
{isLoaded = true; break;}
}
//..........
if(!isLoaded)
return false;
}
return true;
};
setTimeout(function(){alert(IsLoadedFonts('myfont'));},100);
</script>
</body>
Can check many fonts:
setTimeout(function(){alert(IsLoadedFonts('font1','font2','font3'));},100);
The below code works in opera only but is easy:
if(!document.defaultView.getComputedStyle(document.getElementById('mydiv'))['fontFamily'].match(/myfont/i))
alert("font do not loaded ");