In Javascript, I know how to set up a drag & drop target that accepts file uploads from the user's computer. How can I set up a drop target that accepts images that are dragged from another website? All I need to know is the URL of the image that they've dragged.
I know this is possible, since Google Docs accepts image drops from other websites. Any idea how they're doing it?
UPDATE:
It looks like there are differences between Chrome on Windows and MacOS. On Windows dataTransfer.getData('Text'); works but not on MacOS. dataTransfer.getData('URL'); should work on both.
OLD answer:
You could define a drop zone:
<div id="dropbox">DropZone => you could drop any image from any page here</div>
and then handle the dragenter, dragexit, dragover and drop events:
var dropbox = document.getElementById('dropbox');
dropbox.addEventListener('dragenter', noopHandler, false);
dropbox.addEventListener('dragexit', noopHandler, false);
dropbox.addEventListener('dragover', noopHandler, false);
dropbox.addEventListener('drop', drop, false);
function noopHandler(evt) {
evt.stopPropagation();
evt.preventDefault();
}
function drop(evt) {
evt.stopPropagation();
evt.preventDefault();
var imageUrl = evt.dataTransfer.getData('Text');
alert(imageUrl);
}
It is inside the drop event handler that we are reading the image data from the dataTransfer object as Text. If we dropped an image from some other webpage this text will represent the url of the image.
And here's a live demo.
function drop(evt) {
evt.stopPropagation();
evt.preventDefault();
var imageUrl = evt.dataTransfer.getData('URL'); // instead of 'Text'
alert(imageUrl);
}
Seems to work in Firefox, Safari, and Chrome on Mac. Also works in Firefox, IE, and Chrome in Windows.
Updated fiddle
Although you are able to accept the drag and drop of an image from another website, you are unable to do any processing of it (e.g. converting it to a base64 string using the canvas) (as of 21st August 2014) because of various cross-origin policy issues.
var dt = event.dataTransfer;
var url = dt.getData('url');
if (!url) {
url = dt.getData('text/plain');
if (!url) {
url = dt.getData('text/uri-list');
if (!url) {
// We have tried all that we can to get this url but we can't. Abort mission
return;
}
}
}
Even Google can't get around this - If you use gmail, you can drag and drop an image from another location in to the email body, but all this does is create an <img/> element with its src set to url (from the code above).
However, I've created a plugin that allows you to fake it cross-origin drag and drop. It requires a PHP backend.
Read the article I wrote on it here https://coderwall.com/p/doco6w/html5-cross-origin-drag-and-drop
Here's my solution to the problem: Dropzone js - Drag n Drop file from same page
Please do keep in mind that ability to drag an image from another domain depends on their CORS setup.
Some browsers use text/plain some use text/html as well
This code should pull any text or image source url on the latest Chrome, FF on Mac and PC.
Safari doesn't seem to give the URL so if someone knows how to get it let me know.
I'm still working on IE.
function getAnyText(theevent) {
//see if we have anything in the text or img src tag
var insert_text;
var location = theevent.target;
var etext;
var ehtml;
try {
etext = theevent.dataTransfer.getData("text/plain");
} catch (_error) {}
try {
ehtml = theevent.dataTransfer.getData("text/html");
} catch (_error) {}
if (etext) {
insert_text = etext;
} else if (ehtml) {
object = $('<div/>').html(ehtml).contents();
if (object) {
insert_text = object.closest('img').prop('src');
}
}
if (insert_text) {
insertText(insert_text,location);
}
}
As the other answers correctly state: It normally depends on the CORS-Settings of the server if drag & drop from another browser window is possible (Access-Control-Allow-Origin has to be set).
However I've just found out by chance that it's possible to drap & drop any images from a Firefox (current version 68.0.1) to a Chrome window (current version 76.0.3809) and process it in Javascript, regardless if CORS headers are set or not.
See working example (based on jsfiddle of Darin Dimitrov) which accepts and directly shows images from:
drag and drop from local computer (e.g. from file explorer)
drag and drop from other website, if CORS headers are set, e.g. an image from https://imgur.com/
drag and drop any images from Firefox window to Chrome window:
open jsfiddle demo in Chrome
open e.g. google image search in Firefox and search for any image
click on the image for bigger preview
drag & drop the preview image to the jsfiddle demo in Chrome
However this seems to be kind of a bug of Firefox and therefore I would not rely on this behaviour in an productive application.
Related
Some websites have lots of images, so lazyloading seems appropiate to reduce load times and data consumption. But what if you also need to support printing for that website?
I mean, you can try to detect the print event and then load the images, with something like this:
HTML
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7">
Note: this is a one by one pixels gif dummy image.
JavaScript
window.addEventListener('DOMContentLoaded', () => {
img = document.querySelector('img');
var isPrinting = window.matchMedia('print');
isPrinting.addListener((media) => {
if (media.matches) {
img.src = 'http://unsplash.it/500/300/?image=705';
}
})
});
Note: if you try this in a code playground, remove the DOMContentLoaded event (or simply fork these: JSFiddle | Codepen).
Note: I didn't event bother with the onbeforeprint and onafterprint for obvious reasons.
This will work fine if the image is cached, but then again that's precisely not the point. The image/s should all load and then appear in the printing screen.
Do you have any ideas? Has anyone successfully implemented a print-ready lazyloading plugin?
Update
I've tried redirecting the user after the print dialog is detected, to a flagged version of the website a.k.a website.com?print=true where lazyloading is deactivated and all images load normally.
This method is improved by applying the window.print() method in this flagged print-ready version of the page, opening a new print dialog once all images are finished loading, and showing a "wait for it" message in the meantime at the top of the page.
Important note: this method was tested in Chrome, it does not work in Firefox nor Edge (hence why this is not an answer, but a testimony).
It works in Chrome beacuse the print dialog closes when you redirect to another website (in this case same url but flagged). In Edge and Firefox the print dialog is an actual window and it does not close it, making it pretty unusable.
Based on your desired functionality, I'm not quite sure what you want to do is feasible. As a developer we don't really have control over a users browser. Here are my thoughts as to why this isn't fully possible.
Hooking the event to go and load your missing images won't let you guarantee images will make it from the server into your page. More specifically, the PDF generated for your print preview is going to get generated before your image(s) is done loading, the img.src = "..." is asynchronous. You'd run into similar issues with onbeforeprint as well, unfortunately. Sometimes it works, sometimes it does not (example, your fiddle worked when testing in safari, but did not in Chrome)
You cannot stall or stop the print call -- you can't force the browser to wait for your image to finish loading in the lazy loading context. (I read something about using alerts to achieve this once, but it seemed really hacky to me, was more of a deterrent to printing than stalling)
You cannot force img.src to get that data synchronously in a lazy-load context. There are some methods of doing this, but they are clever hacks -- referenced as pure evil and may not work in always. I found another link with a similar approach
So we have a problem, if the images are not loaded by the time print event is fired, we cannot force the browser to wait until they are done. Sure, we can hook and go get those images on print, but as above points out, we cannot wait for those resources to load before the print preview pops up.
Potential solution (inspired by links in point three as well as this link)
You could almost get away with doing a synchronous XMLHttpRequest. Syncrhonous XMLHTTPRequests will not let you change the responseType, they are always strings. However, you could convert the string value to an arrayBuffer encode it to a base-64 encoded string, and set the src to a dataURL (see the link that referenced clever hacks) -- however, when I tried this I got an error in the jsfiddle -- so it would be possible, if things were configured correctly, in theory. I'm hesitant to say yes you can, since I wasn't able to get the fiddle working with the following (but it's a route you could explore!).
var xhr = new XMLHttpRequest();
xhr.open("GET","http://unsplash.it/500/300/?image=705",false);
xhr.send(null);
if (request.status === 200) {
//we cannot change the resposne type in synchronous XMLHTTPRequests
//we can convert the string into a dataURL though
var arr = new Uint8Array(this.response);
// Convert the int array to a binary string
// We have to use apply() as we are converting an *array*
// and String.fromCharCode() takes one or more single values, not
// an array.
var raw = String.fromCharCode.apply(null,arr);
// This is supported in modern browsers
var b64=btoa(raw);
var dataURL="data:image/jpeg;base64,"+b64;
img.src = dataURL;
}
Work around to enhance the user experience
Something you could do is have some text that only displays in the print version of your page (via #print css media) that says "images are still loading, cancel your print request and try again" and when the images are finished loading, remove that "still waiting on resources try again message" from the DOM. Farther, you could wrap your main content inside an element that inverses the display to none when content is not loaded, so all you see is that message in the print preview dialog.
Going off of the code you posted this could look something like the following (see updated jsfiddle):
CSS
.printing-not-ready-message{
display:none;
}
#media print{
.printing-not-ready-message{
display:block;
}
.do-not-print-content{
display:none;
}
}
HTML
<div class="printing-not-ready-message">
Images are still loading please cancel your preview and try again shortly.
</div>
<div class="do-not-print-content">
<h1>Welcome to my Lazy Page</h1>
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7">
<p>Insert some comment about picture</p>
</div>
JavaScript
window.addEventListener('DOMContentLoaded', () => {
img = document.querySelector('img');
var isPrinting = window.matchMedia('print');
isPrinting.addListener((media) => {
if (media.matches) {
img.src = 'http://unsplash.it/500/300/?image=705';
//depending on how the lazy loading is done, the following might
//exist in some other call, should happen after all images are loaded.
//There is only 1 image in this example so this code can be called here.
img.onload = ()=>{
document.querySelector(".printing-not-ready-message").remove();
document.querySelector(".do-not-print-content").className=""
}
}
})
});
I'm the author of the vanilla-lazyload script and I've recently developed a feature that makes print of all images possible!
Tested cross browser using this repo code which is live here.
Take a look and let me know what you think!
I'm open to pull requests on GitHub of course.
I wrote a lazy loading jquery plugin that supports showing images on print using the window.onbeforeprint events and mediaQueryListeners.
https://github.com/msigley/Unveil-EX/
//declare custom onbeforeprint method
const customOnBeforePrint = () => {
const smoothScroll = (h) => {
let i = h || 0;
if (i < 200) {
setTimeout(() => {
window.scrollTo(window.scrollY, window.scrollY + i);
smoothScroll(i + 10);
}, 10);
}
};
let height = document.body.scrollHeight;
let newHeight;
while (true) {
smoothScroll(100);
if (newHeight === height) break;
height = newHeight;
}
};
//override the onbeforeprint method
window.onbeforeprint = customOnBeforePrint;
Copy&Paste that block into devtool's console and then try to click print button. That workaround is working for me.
For whoever is in the same boat as I was: when using the browser native loading="lazy", you can simply remove that attribute when printing is going to happen. Below is my jQuery implementation.
window.onbeforeprint = function () {
$('img').each(function () {
$(this).removeAttr('loading')
});
}
Chrome will then just load all images and they will show up when printing.
I made trying to make my first addon but I have run into an issue. My addon is an addon to upload images and videos to a website. The images work fine, but with videos, it is a little off. Here is my code:
var contextMenu = require("sdk/context-menu");
var tabs = require("sdk/tabs");
contextMenu.Item({
label: "Upload to domain",
context: contextMenu.SelectorContext("img,video"),
contentScript:'self.on("click", function(node, data){self.postMessage(node.src);});',
onMessage: function(imgSrc){
tabs.open("http://domain.org/upload/?"+imgSrc);
}
});
If you play the video on the right here, the video url gets passed to the onMessage function fine. If you try it here, it doesn't pass a url. They are both webms. I am guessing that it just doesn't work with an html5 player. Am I correct? Is there a workaround for this?
your method of finding source does not always work with video element because, unlike img, the video element's source can be specified as child element source which is what is happening in the second case. To handle that scenario, your code must be something like:
self.on("click", function(node, data){
if(node.src){
self.postMessage(node.src);
}else if(node.nodeName.toUpperCase() === "video".toUpperCase()){
var sources = node.children; // or may be node.querySelector("source");
for(var i in sources){
if(sources[i].src && sources[i].nodeName.toUpperCase() === "source".toUpperCase()){
self.postMessage(node.src);
}
}
}
}
I originally created a button that changed the src based on mouseup and mousedown to give it the appearance of being depressed when clicked as well as playing a click sound. This worked out fine until I tried to force PDF download through MVC action. I've tried to skin this cat a few different ways but I've settled on using the mouseup function to set the window location to my pdf download action passing in the needed file download path information. Now when I click the button it depresses (correctly executing mousedown), then on mouseup it correctly plays the click sound and also downloads the pdf file as I want, but makes the button image appear to be broken.
When I first load the page and the button hasn't been clicked yet the image looks like it's supposed to. Then after running mouseup it's broken (using Chrome or disappears or appears garbled in IE and Safari) but when I inspect the source the src attribute looks to be identical so I'm not sure why it's returning a broken image if the src is the same as it was before.
Code-
View jquery:
var audio = document.getElementsByTagName("audio")[0];
$('#MAAX-DGB-Button').mouseup(function () {
$(this).attr('src', '../../Content/Images/AdLandingViews/MAAX-DGB.png');
audio.play();
var pdfDownload = '/Locator/ForcePDFDownload?PDFURL=<file location info>';
window.location = pdfDownload;
});
$('#MAAX-DGB-Button').mousedown(function () {
$(this).attr('src', '../../Content/Images/AdLandingViews/MAAX-DGB_press.png');
});
View HTML:
<img src="../../Content/Images/AdLandingViews/MAAX-DGB.png" alt="Download MAAX Collection Brochure" id="MAAX-DGB-Button" />
ForcePDFDownload MVC Action:
public ActionResult ForcePDFDownload(string PDFURL)
{
string path = #"\\<server location>\" + PDFURL;
string filename = Path.GetFileName(PDFURL);
Response.AppendHeader("Content-Disposition", "attachment; filename=" + filename);
return File(path, "application/pdf");
}
You can test the code here:
https://maaxspasportal.com/Locator/AdLandingLocate/MAAXCollection but you'll have to fill in some dummy data and submit the form to get to the results page where this issue is occurring on the "Download Green Brochure" button.
Tested Browsers:
Chrome Version 28.0.1500.95 - Image appears broken.
IE 10 - Image disappears or looks garbled.
Safari 6.0.5 - Image disappears.
FireFox 22.0 - Works!
Thanks for any help getting this to work correctly.
Well I never determined the exact root cause for the issues, however I did find a way to fix the problem.
On mouseup I applied a setTimeout delay on the forced pdf download function:
var audio = document.getElementsByTagName("audio")[0];
$('#MAAX-DGB-Button').mouseup(function () {
$(this).attr('src', '../../Content/Images/AdLandingViews/MAAX-DGB.png');
audio.play();
var pdfDownload = '/Locator/ForcePDFDownload?PDFURL=assets.maaxspas.com/docsources/1/138ee0a8-e0f0-47d5-9447-fca6f538d74f.pdf';
setTimeout(function() { window.location = pdfDownload }, 1000);
});
$('#MAAX-DGB-Button').mousedown(function () {
$(this).attr('src', '../../Content/Images/AdLandingViews/MAAX-DGB_press.png');
});
Would still be interested to know what was going wrong but at least it's functioning the way I want now.
Thanks.
I'm a java guy trying my hand in javascript and need some help. I came across an amazing tutorial on image uploads here Mozilla Tutorial and need some help figuring it out. I am currently working on the drag and drop image upload feature. Every time I drag an image onto my area the mouse turns green so it's activated. But then when I let go it should send me an alert that says one image was found. However it always just alerts 0. So the size of the array is 0. Any ideas? Thanks for taking a look. What I've tried with no success...
Copying and pasting the code from the tutorial into my javascript file exactly
Moving the code to add the listeners outside of a function and into a window onload
Every browser I have
...
function toggleStrideMedia()
{
if(getDisplay("strideMediaWrapper") == "" || getDisplay("strideMediaWrapper") == "none")
{
show("strideMediaWrapper");
getElement("strideMediaDropZone").addEventListener("dragenter", dragenter, false);
getElement("strideMediaDropZone").addEventListener("dragover", dragover, false);
getElement("strideMediaDropZone").addEventListener("drop", drop, false);
}
else
{
hide("strideMediaWrapper");
}
}
function dragenter(e)
{
e.stopPropagation();
e.preventDefault();
}
function dragover(e)
{
e.stopPropagation();
e.preventDefault();
}
function drop(e)
{
e.stopPropagation();
e.preventDefault();
var dt = e.dataTransfer;
var files = dt.files;
// THIS SHOULD BE GIVING ME A ONE BUT IT ALWAYS GIVES ME A ZERO INSTEAD
alert(files.length);
handleFiles(files);
}
.
UPDATE - Fiddle Results
UPDATE
The actual problem turned out to be that if you try to drag images directly from one web browser tab to this web based drag and drop interface, the event will fire but no files will be dropped. The asker noted this issue on OSX and I was able to replicate the same behavior in Windows 7.
Without seeing your HTML, it's hard to tell what you were having difficulty with. If the ondragover/ondragenter piece wasn't set up correctly then dropping won't work, but you wouldn't see an alert at all, you'd just see the browser render the image from the local filesystem. That also means that you're almost certainly successfully adding the drop event to the correct element.
Try this Fiddle and see if it works for you: http://jsfiddle.net/qey9G/4/
HTML
<div>
<div id="dropzone" style="margin:30px; width:500px; height:300px;
border:1px dotted grey;">
Drag & drop your file here...
</div>
</div>
JavaScript
var dropzone = document.getElementById("dropzone");
dropzone.ondragover = dropzone.ondragenter = function(event) {
event.stopPropagation();
event.preventDefault();
}
dropzone.ondrop= function drop(e)
{
e.stopPropagation();
e.preventDefault();
var dt = e.dataTransfer;
var files = dt.files;
alert(files.length);
}
I have a div with border and in its right-bottom corner I have image for resizing:
So when user presses mouse on the image, he (or she) can drag mouse and resize the div.
This works fine in all browsers but FireFox.
In FireFox something strange happens: after the user presses mouse and starts dragging, the cursor changes to:
So the cursor changes to this one and mouse move events are not coming, when the mouse is dragged.
I am wondering, what causes this behaviour. I thought maybe FireFox thinks that the user is trying to select text by pressing and dragging the mouse. But I cancelled text selection using this code:
resizeImageImg.onselectstart = "return false;";
resizeImageImg.ondragstart = "return false;";
resizeImageImg.style.WebkitUserSelect = 'none';
resizeImageImg.style.KhtmlUserSelect = 'none';
resizeImageImg.style.MozUserSelect = 'none';
resizeImageImg.style.MsUserSelect = 'none';
resizeImageImg.style.OUserSelect = 'none';
resizeImageImg.style.UserSelect = 'none';
resizeImageImg.setAttribute ("unselectable", "on");
resizeImageImg.setAttribute ("draggable", "false");
(for both: the div and the resize image)
But this did not solve the problem. FireFox still does not let resizing and changes cursor to "not-allowed".
Can anybody please help?
Thank you all, I found the solution.
I replaced:
resizeImageImg.ondragstar = "return false;";
by
resizeImageImg.ondragstart = function () { return false; };
and it started working in FireFox as well.
What happens here is that if you want to process mouse-move events when your mouse-down event came from an image, then you have to make you image not-draggable. But this is not enough to use
resizeImageImg.setAttribute ("draggable", false);
(at least in FireFox) becasuse events ondragstart are still coming. I understood this when I set:
resizeImageImg.ondragstart = function () { alert ("ondragstart"); return false; };
So I realized that FireFox does not obbey setAttribute ("draggable", false) - whilst other browsers do.
Andy, here is the solution I have come up with. I have gone to great effort to make it quick and easy to use.
You can view the file here:
http://files.social-library.org/stackoverflow/imageResizer.html
It is simple to use. Create your image and specify a width and height. Then, once the page loads call the function imageResizer.init(imageObject) sending the image object as a parameter. It will then set the image up with the dragger.
This works in firefox, chrome and internet explorer 8+.