I use this code bellow for signature input, the signature pad working fine with web browser but not working means not draw on mobile browser.
I use this code bellow for signature input, the signature pad working fine with web browser but not working means not draw on mobile browser.
<script type="text/javascript">
/*! http://keith-wood.name/signature.html
Signature plugin for jQuery UI v1.2.1.
Requires excanvas.js in IE.
Written by Keith Wood (wood.keith{at}optusnet.com.au) April 2012.
Available under the MIT (http://keith-wood.name/licence.html) license.
Please attribute the author if you use it. */
/* globals G_vmlCanvasManager */
(function($) { // Hide scope, no $ conflict
'use strict';
var signatureOverrides = {
options: {
distance: 0,
background: '#fff',
color: '#000',
thickness: 2,
guideline: false,
guidelineColor: '#a0a0a0',
guidelineOffset: 50,
guidelineIndent: 10,
notAvailable: 'Your browser doesn\'t support signing',
scale: 1,
syncField: null,
syncFormat: 'JSON',
svgStyles: false,
change: null
},
/** Initialise a new signature area.
#memberof Signature
#private */
_create: function() {
this.element.addClass(this.widgetFullName || this.widgetBaseClass);
try {
this.canvas = $('<canvas width="' + this.element.width() + '" height="' +
this.element.height() + '">' + this.options.notAvailable + '</canvas>')[0];
this.element.append(this.canvas);
}
catch (e) {
$(this.canvas).remove();
this.resize = true;
this.canvas = document.createElement('canvas');
this.canvas.setAttribute('width', this.element.width());
this.canvas.setAttribute('height', this.element.height());
this.canvas.innerHTML = this.options.notAvailable;
this.element.append(this.canvas);
/* jshint -W106 */
if (G_vmlCanvasManager) { // Requires excanvas.js
G_vmlCanvasManager.initElement(this.canvas);
}
/* jshint +W106 */
}
this.ctx = this.canvas.getContext('2d');
this._refresh(true);
this._mouseInit();
},
/** Refresh the appearance of the signature area.
#memberof Signature
#private
#param {boolean} init <code>true</code> if initialising. */
_refresh: function(init) {
if (this.resize) {
var parent = $(this.canvas);
$('div', this.canvas).css({width: parent.width(), height: parent.height()});
}
this.ctx.fillStyle = this.options.background;
this.ctx.strokeStyle = this.options.color;
this.ctx.lineWidth = this.options.thickness;
this.ctx.lineCap = 'round';
this.ctx.lineJoin = 'round';
this.clear(init);
},
/** Clear the signature area.
#memberof Signature
#param {boolean} init <code>true</code> if initialising - internal use only.
#example $(selector).signature('clear') */
clear: function(init) {
if (this.options.disabled) {
return;
}
this.ctx.clearRect(0, 0, this.element.width(), this.element.height());
this.ctx.fillRect(0, 0, this.element.width(), this.element.height());
if (this.options.guideline) {
this.ctx.save();
this.ctx.strokeStyle = this.options.guidelineColor;
this.ctx.lineWidth = 1;
this.ctx.beginPath();
this.ctx.moveTo(this.options.guidelineIndent,
this.element.height() - this.options.guidelineOffset);
this.ctx.lineTo(this.element.width() - this.options.guidelineIndent,
this.element.height() - this.options.guidelineOffset);
this.ctx.stroke();
this.ctx.restore();
}
this.lines = [];
if (!init) {
this._changed();
}
},
/** Synchronise changes and trigger a change event.
#memberof Signature
#private
#param {Event} event The triggering event. */
_changed: function(event) {
if (this.options.syncField) {
var output = '';
switch (this.options.syncFormat) {
case 'PNG':
output = this.toDataURL();
break;
case 'JPEG':
output = this.toDataURL('image/jpeg');
break;
case 'SVG':
output = this.toSVG();
break;
default:
output = this.toJSON();
}
$(this.options.syncField).val(output);
}
this._trigger('change', event, {});
},
/** Refresh the signature when options change.
#memberof Signature
#private
#param {object} options The new option values. */
_setOptions: function(/* options */) {
if (this._superApply) {
this._superApply(arguments); // Base widget handling
}
else {
$.Widget.prototype._setOptions.apply(this, arguments); // Base widget handling
}
var count = 0;
var onlyDisable = true;
for (var name in arguments[0]) {
if (arguments[0].hasOwnProperty(name)) {
count++;
onlyDisable = onlyDisable && name === 'disabled';
}
}
if (count > 1 || !onlyDisable) {
this._refresh();
}
},
/** Determine if dragging can start.
#memberof Signature
#private
#param {Event} event The triggering mouse event.
#return {boolean} <code>true</code> if allowed, <code>false</code> if not */
_mouseCapture: function(/* event */) {
return !this.options.disabled;
},
/** Start a new line.
#memberof Signature
#private
#param {Event} event The triggering mouse event. */
_mouseStart: function(event) {
this.offset = this.element.offset();
this.offset.left -= document.documentElement.scrollLeft || document.body.scrollLeft;
this.offset.top -= document.documentElement.scrollTop || document.body.scrollTop;
this.lastPoint = [this._round(event.clientX - this.offset.left),
this._round(event.clientY - this.offset.top)];
this.curLine = [this.lastPoint];
this.lines.push(this.curLine);
},
/** Track the mouse.
#memberof Signature
#private
#param {Event} event The triggering mouse event. */
_mouseDrag: function(event) {
var point = [this._round(event.clientX - this.offset.left),
this._round(event.clientY - this.offset.top)];
this.curLine.push(point);
this.ctx.beginPath();
this.ctx.moveTo(this.lastPoint[0], this.lastPoint[1]);
this.ctx.lineTo(point[0], point[1]);
this.ctx.stroke();
this.lastPoint = point;
},
/** End a line.
#memberof Signature
#private
#param {Event} event The triggering mouse event. */
_mouseStop: function(event) {
if (this.curLine.length === 1) {
event.clientY += this.options.thickness;
this._mouseDrag(event);
}
this.lastPoint = null;
this.curLine = null;
this._changed(event);
},
/** Round to two decimal points.
#memberof Signature
#private
#param {number} value The value to round.
#return {number} The rounded value. */
_round: function(value) {
return Math.round(value * 100) / 100;
},
/** Convert the captured lines to JSON text.
#memberof Signature
#return {string} The JSON text version of the lines.
#example var json = $(selector).signature('toJSON') */
toJSON: function() {
return '{"lines":[' + $.map(this.lines, function(line) {
return '[' + $.map(line, function(point) {
return '[' + point + ']';
}) + ']';
}) + ']}';
},
/** Convert the captured lines to SVG text.
#memberof Signature
#return {string} The SVG text version of the lines.
#example var svg = $(selector).signature('toSVG') */
toSVG: function() {
var attrs1 = (this.options.svgStyles ? 'style="fill: ' + this.options.background + ';"' :
'fill="' + this.options.background + '"');
var attrs2 = (this.options.svgStyles ?
'style="fill: none; stroke: ' + this.options.color + '; stroke-width: ' + this.options.thickness + ';"' :
'fill="none" stroke="' + this.options.color + '" stroke-width="' + this.options.thickness + '"');
return '<?xml version="1.0"?>\n<!DOCTYPE svg PUBLIC ' +
'"-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n' +
'<svg xmlns="http://www.w3.org/2000/svg" width="15cm" height="15cm">\n' +
' <g ' + attrs1 + '>\n' +
' <rect x="0" y="0" width="' + this.canvas.width + '" height="' + this.canvas.height + '"/>\n' +
' <g ' + attrs2 + '>\n'+
$.map(this.lines, function(line) {
return ' <polyline points="' +
$.map(line, function(point) { return point + ''; }).join(' ') + '"/>\n';
}).join('') +
' </g>\n </g>\n</svg>\n';
},
/** Convert the captured lines to an image encoded in a <code>data:</code> URL.
#memberof Signature
#param {string} [type='image/png'] The MIME type of the image.
#param {number} [quality=0.92] The image quality, between 0 and 1.
#return {string} The signature as a data: URL image.
#example var data = $(selector).signature('toDataURL', 'image/jpeg') */
toDataURL: function(type, quality) {
return this.canvas.toDataURL(type, quality);
},
/** Draw a signature from its JSON or SVG description or <code>data:</code> URL.
<p>Note that drawing a <code>data:</code> URL does not reconstruct the internal representation!</p>
#memberof Signature
#param {object|string} sig An object with attribute <code>lines</code> being an array of arrays of points
or the text version of the JSON or SVG or a <code>data:</code> URL containing an image.
#example $(selector).signature('draw', sigAsJSON) */
draw: function(sig) {
if (this.options.disabled) {
return;
}
this.clear(true);
if (typeof sig === 'string' && sig.indexOf('data:') === 0) { // Data URL
this._drawDataURL(sig, this.options.scale);
} else if (typeof sig === 'string' && sig.indexOf('<svg') > -1) { // SVG
this._drawSVG(sig, this.options.scale);
} else {
this._drawJSON(sig, this.options.scale);
}
this._changed();
},
/** Draw a signature from its JSON description.
#memberof Signature
#private
#param {object|string} sig An object with attribute <code>lines</code> being an array of arrays of points
or the text version of the JSON.
#param {number} scale A scaling factor. */
_drawJSON: function(sig, scale) {
if (typeof sig === 'string') {
sig = $.parseJSON(sig);
}
this.lines = sig.lines || [];
var ctx = this.ctx;
$.each(this.lines, function() {
ctx.beginPath();
$.each(this, function(i) {
ctx[i === 0 ? 'moveTo' : 'lineTo'](this[0] * scale, this[1] * scale);
});
ctx.stroke();
});
},
/** Draw a signature from its SVG description.
#memberof Signature
#private
#param {string} sig The text version of the SVG.
#param {number} scale A scaling factor. */
_drawSVG: function(sig, scale) {
var lines = this.lines = [];
$(sig).find('polyline').each(function() {
var line = [];
$.each($(this).attr('points').split(' '), function(i, point) {
var xy = point.split(',');
line.push([parseFloat(xy[0]), parseFloat(xy[1])]);
});
lines.push(line);
});
var ctx = this.ctx;
$.each(this.lines, function() {
ctx.beginPath();
$.each(this, function(i) {
ctx[i === 0 ? 'moveTo' : 'lineTo'](this[0] * scale, this[1] * scale);
});
ctx.stroke();
});
},
/** Draw a signature from its <code>data:</code> URL.
<p>Note that this does not reconstruct the internal representation!</p>
#memberof Signature
#private
#param {string} sig The <code>data:</code> URL containing an image.
#param {number} scale A scaling factor. */
_drawDataURL: function(sig, scale) {
var image = new Image();
var context = this.ctx;
image.onload = function() {
context.drawImage(this, 0, 0, image.width * scale, image.height * scale);
};
image.src = sig;
},
/** Determine whether or not any drawing has occurred.
#memberof Signature
#return {boolean} <code>true</code> if not signed, <code>false</code> if signed.
#example if ($(selector).signature('isEmpty')) ... */
isEmpty: function() {
return this.lines.length === 0;
},
/** Remove the signature functionality.
#memberof Signature
#private */
_destroy: function() {
this.element.removeClass(this.widgetFullName || this.widgetBaseClass);
$(this.canvas).remove();
this.canvas = this.ctx = this.lines = null;
this._mouseDestroy();
}
};
if (!$.Widget.prototype._destroy) {
$.extend(signatureOverrides, {
/* Remove the signature functionality. */
destroy: function() {
this._destroy();
$.Widget.prototype.destroy.call(this); // Base widget handling
}
});
}
if ($.Widget.prototype._getCreateOptions === $.noop) {
$.extend(signatureOverrides, {
/* Restore the metadata functionality. */
_getCreateOptions: function() {
return $.metadata && $.metadata.get(this.element[0])[this.widgetName];
}
});
}
$.widget('kbw.signature', $.ui.mouse, signatureOverrides);
// Make some things more accessible
$.kbw.signature.options = $.kbw.signature.prototype.options;
})(jQuery);
</script>
<script>
var sig = $('#sig').signature({syncField: '#signature64', syncFormat: 'PNG'});
$('#clear').click(function(e) {
e.preventDefault();
sig.signature('clear');
$("#signature64").val('');
});
</script>
I use it laravel, hope anyone can help me.
I was also stuck at this, try following:
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui-touch-punch/0.2.3/jquery.ui.touch-punch.min.js" ></script>
Related
I want to build a UI with a joystick, To implement the joystick, I used the JoyStick js library: https://github.com/bobboteck/JoyStick
My code works in Firefox and Chrome on Windows 10, but on Android the joystick doesn't appear with the following error messages:
(index):1889 crbug/1173575, non-JS module files deprecated.
(anonymous) # (index):1889
chromewebdata/:1 Not allowed to load local resource: content://0#media/external/file/10769
My code:
controller.html
<!DOCTYPE html>
<html>
<head>
<title>drone controller</title>
<meta charset="utf-8">
<script src="joy.js"></script>
</head>
<body>
<!-- Example of FIXED or ABSOLUTE position -->
<div id="container" style="width:200px;height:200px;margin:50px;position:fixed;bottom:30px;left:500px;">
</div>
<div style="position:fixed;bottom:125px;left:750px;">
Posizione X:<input id="joy3PosizioneX" type="text"><br>
Posizione Y:<input id="joy3PosizioneY" type="text"><br>
Direzione:<input id="joy3Direzione" type="text"><br>
X :<input id="joy3X" type="text"><br>
Y :<input id="joy3Y" type="text">
</div>
<script>
var joy3 = new JoyStick('container');
// var joy3Param = { "title": "joystick3" };
// var Joy3 = new JoyStick('joy3Div', joy3Param);
var joy3IinputPosX = document.getElementById("joy3PosizioneX");
var joy3InputPosY = document.getElementById("joy3PosizioneY");
var joy3Direzione = document.getElementById("joy3Direzione");
var joy3X = document.getElementById("joy3X");
var joy3Y = document.getElementById("joy3Y");
setInterval(function(){ joy3IinputPosX.value=joy3.GetPosX(); }, 50);
setInterval(function(){ joy3InputPosY.value=joy3.GetPosY(); }, 50);
setInterval(function(){ joy3Direzione.value=joy3.GetDir(); }, 50);
setInterval(function(){ joy3X.value=joy3.GetX(); }, 50);
setInterval(function(){ joy3Y.value=joy3.GetY(); }, 50);
</script>
</body>
</html>
joy.js unmodified from github
var JoyStick = (function(container, parameters)
{
parameters = parameters || {};
var title = (typeof parameters.title === "undefined" ? "joystick" : parameters.title),
width = (typeof parameters.width === "undefined" ? 0 : parameters.width),
height = (typeof parameters.height === "undefined" ? 0 : parameters.height),
internalFillColor = (typeof parameters.internalFillColor === "undefined" ? "#00AA00" : parameters.internalFillColor),
internalLineWidth = (typeof parameters.internalLineWidth === "undefined" ? 2 : parameters.internalLineWidth),
internalStrokeColor = (typeof parameters.internalStrokeColor === "undefined" ? "#003300" : parameters.internalStrokeColor),
externalLineWidth = (typeof parameters.externalLineWidth === "undefined" ? 2 : parameters.externalLineWidth),
externalStrokeColor = (typeof parameters.externalStrokeColor === "undefined" ? "#008000" : parameters.externalStrokeColor),
autoReturnToCenter = (typeof parameters.autoReturnToCenter === "undefined" ? true : parameters.autoReturnToCenter);
// Create Canvas element and add it in the Container object
var objContainer = document.getElementById(container);
var canvas = document.createElement("canvas");
canvas.id = title;
if(width === 0) { width = objContainer.clientWidth; }
if(height === 0) { height = objContainer.clientHeight; }
canvas.width = width;
canvas.height = height;
objContainer.appendChild(canvas);
var context=canvas.getContext("2d");
var pressed = 0; // Bool - 1=Yes - 0=No
var circumference = 2 * Math.PI;
var internalRadius = (canvas.width-((canvas.width/2)+10))/2;
var maxMoveStick = internalRadius + 5;
var externalRadius = internalRadius + 30;
var centerX = canvas.width / 2;
var centerY = canvas.height / 2;
var directionHorizontalLimitPos = canvas.width / 10;
var directionHorizontalLimitNeg = directionHorizontalLimitPos * -1;
var directionVerticalLimitPos = canvas.height / 10;
var directionVerticalLimitNeg = directionVerticalLimitPos * -1;
// Used to save current position of stick
var movedX=centerX;
var movedY=centerY;
// Check if the device support the touch or not
if("ontouchstart" in document.documentElement)
{
canvas.addEventListener("touchstart", onTouchStart, false);
canvas.addEventListener("touchmove", onTouchMove, false);
canvas.addEventListener("touchend", onTouchEnd, false);
}
else
{
canvas.addEventListener("mousedown", onMouseDown, false);
canvas.addEventListener("mousemove", onMouseMove, false);
canvas.addEventListener("mouseup", onMouseUp, false);
}
// Draw the object
drawExternal();
drawInternal();
/******************************************************
* Private methods
*****************************************************/
/**
* #desc Draw the external circle used as reference position
*/
function drawExternal()
{
context.beginPath();
context.arc(centerX, centerY, externalRadius, 0, circumference, false);
context.lineWidth = externalLineWidth;
context.strokeStyle = externalStrokeColor;
context.stroke();
}
/**
* #desc Draw the internal stick in the current position the user have moved it
*/
function drawInternal()
{
context.beginPath();
if(movedX<internalRadius) { movedX=maxMoveStick; }
if((movedX+internalRadius) > canvas.width) { movedX = canvas.width-(maxMoveStick); }
if(movedY<internalRadius) { movedY=maxMoveStick; }
if((movedY+internalRadius) > canvas.height) { movedY = canvas.height-(maxMoveStick); }
context.arc(movedX, movedY, internalRadius, 0, circumference, false);
// create radial gradient
var grd = context.createRadialGradient(centerX, centerY, 5, centerX, centerY, 200);
// Light color
grd.addColorStop(0, internalFillColor);
// Dark color
grd.addColorStop(1, internalStrokeColor);
context.fillStyle = grd;
context.fill();
context.lineWidth = internalLineWidth;
context.strokeStyle = internalStrokeColor;
context.stroke();
}
/**
* #desc Events for manage touch
*/
function onTouchStart(event)
{
pressed = 1;
}
function onTouchMove(event)
{
// Prevent the browser from doing its default thing (scroll, zoom)
event.preventDefault();
if(pressed === 1 && event.targetTouches[0].target === canvas)
{
movedX = event.targetTouches[0].pageX;
movedY = event.targetTouches[0].pageY;
// Manage offset
if(canvas.offsetParent.tagName.toUpperCase() === "BODY")
{
movedX -= canvas.offsetLeft;
movedY -= canvas.offsetTop;
}
else
{
movedX -= canvas.offsetParent.offsetLeft;
movedY -= canvas.offsetParent.offsetTop;
}
// Delete canvas
context.clearRect(0, 0, canvas.width, canvas.height);
// Redraw object
drawExternal();
drawInternal();
}
}
function onTouchEnd(event)
{
pressed = 0;
// If required reset position store variable
if(autoReturnToCenter)
{
movedX = centerX;
movedY = centerY;
}
// Delete canvas
context.clearRect(0, 0, canvas.width, canvas.height);
// Redraw object
drawExternal();
drawInternal();
//canvas.unbind('touchmove');
}
/**
* #desc Events for manage mouse
*/
function onMouseDown(event)
{
pressed = 1;
}
function onMouseMove(event)
{
if(pressed === 1)
{
movedX = event.pageX;
movedY = event.pageY;
// Manage offset
if(canvas.offsetParent.tagName.toUpperCase() === "BODY")
{
movedX -= canvas.offsetLeft;
movedY -= canvas.offsetTop;
}
else
{
movedX -= canvas.offsetParent.offsetLeft;
movedY -= canvas.offsetParent.offsetTop;
}
// Delete canvas
context.clearRect(0, 0, canvas.width, canvas.height);
// Redraw object
drawExternal();
drawInternal();
}
}
function onMouseUp(event)
{
pressed = 0;
// If required reset position store variable
if(autoReturnToCenter)
{
movedX = centerX;
movedY = centerY;
}
// Delete canvas
context.clearRect(0, 0, canvas.width, canvas.height);
// Redraw object
drawExternal();
drawInternal();
//canvas.unbind('mousemove');
}
/******************************************************
* Public methods
*****************************************************/
/**
* #desc The width of canvas
* #return Number of pixel width
*/
this.GetWidth = function ()
{
return canvas.width;
};
/**
* #desc The height of canvas
* #return Number of pixel height
*/
this.GetHeight = function ()
{
return canvas.height;
};
/**
* #desc The X position of the cursor relative to the canvas that contains it and to its dimensions
* #return Number that indicate relative position
*/
this.GetPosX = function ()
{
return movedX;
};
/**
* #desc The Y position of the cursor relative to the canvas that contains it and to its dimensions
* #return Number that indicate relative position
*/
this.GetPosY = function ()
{
return movedY;
};
/**
* #desc Normalizzed value of X move of stick
* #return Integer from -100 to +100
*/
this.GetX = function ()
{
return (100*((movedX - centerX)/maxMoveStick)).toFixed();
};
/**
* #desc Normalizzed value of Y move of stick
* #return Integer from -100 to +100
*/
this.GetY = function ()
{
return ((100*((movedY - centerY)/maxMoveStick))*-1).toFixed();
};
/**
* #desc Get the direction of the cursor as a string that indicates the cardinal points where this is oriented
* #return String of cardinal point N, NE, E, SE, S, SW, W, NW and C when it is placed in the center
*/
this.GetDir = function()
{
var result = "";
var orizontal = movedX - centerX;
var vertical = movedY - centerY;
if(vertical >= directionVerticalLimitNeg && vertical <= directionVerticalLimitPos)
{
result = "C";
}
if(vertical < directionVerticalLimitNeg)
{
result = "N";
}
if(vertical > directionVerticalLimitPos)
{
result = "S";
}
if(orizontal < directionHorizontalLimitNeg)
{
if(result === "C")
{
result = "W";
}
else
{
result += "W";
}
}
if(orizontal > directionHorizontalLimitPos)
{
if(result === "C")
{
result = "E";
}
else
{
result += "E";
}
}
return result;
};
});
Thanks for your help!
I take it you have copied the files to the phone's local storage, and are trying to open them from there in Chrome?
I suspect it not working is intentional. Access to local files can be quite restricted in web browsers in general (since it doesn't really work together with the origin-based security model), and access to local storage on Android is an equally complicated subject.
If you're planning to serve this on the web, and just want to test whether it works in Chrome on Android, the path of least resistance will probably be to just serve it over HTTP. How to do that is probably outside the scope of the question, but for example (as described near the end of this page) Python's http.server module can be used as a simple command-line web server for testing purposes, assuming you have a computer and a phone in the same local network (or use adb port forwarding).
Edit: I've just tested your files using the above method (with python3 -m http.server), and it does show the green joystick on Chrome 92 on Android.
Alternatively, if your end goal is to package this into an Android app, there are specific APIs (such as the WebViewAssetLoader) that allow you to serve the local HTTP and JS assets in a way that will not run into problems like this.
This gamepad API is relatively new and still now experimental. To my knowledge, only latest verson of Crome for android supports it. Update your crome browser on android and try. This should work.
I need your help. I'm currently trying to write a text 2 png library to generate PNGs including inline styling for every single letter. Currently I'm really stuck trying to add a spacing between each letter. At the moment every letter is written like in layers above each one:
Do you have any good ideas how to do this? At the end the letters should be beside each other including the option to pass a letter spacing to modify the spacing later - like line spacing:
let text = '{#ff0000ES}\n{#FF33F0AC}\nR'
fs.writeFileSync('test2.png', text2png(text,
{
color: 'gray',
textAlign: 'center',
lineSpacing: 30,
letterSpacing: 10, // <-- needed
font: '100px sans-serif'
}
));
This is the function I'm working with:
const {registerFont, createCanvas} = require("canvas");
/**
* Convert text to PNG image.
* #param text
* #param [options]
* #param [options.font="30px sans-serif"] css style font
* #param [options.textAlign="left"] text alignment (left, center, right)
* #param [options.color="black"] (or options.textColor) text color
* #param [options.backgroundColor] (or options.bgColor) background color
* #param [options.lineSpacing=0]
* #param [options.letterSpacing=0]
* #param [options.strokeWidth=0]
* #param [options.strokeColor='white']
* #param [options.padding=0] width of the padding area (left, top, right, bottom)
* #param [options.paddingLeft]
* #param [options.paddingTop]
* #param [options.paddingRight]
* #param [options.paddingBottom]
* #param [options.borderWidth=0] width of border (left, top, right, bottom)
* #param [options.borderLeftWidth=0]
* #param [options.borderTopWidth=0]
* #param [options.borderRightWidth=0]
* #param [options.borderBottomWidth=0]
* #param [options.borderColor="black"] border color
* #param [options.localFontPath] path to local font (e.g. fonts/Lobster-Regular.ttf)
* #param [options.localFontName] name of local font (e.g. Lobster)
* #param [options.output="buffer"] 'buffer', 'stream', 'dataURL', 'canvas's
* #returns {string} png image buffer
*/
const text2png = (text, options = {}) => {
// Options
options = parseOptions(options);
// Register a custom font
if (options.localFontPath && options.localFontName) {
registerFont(options.localFontPath, {family: options.localFontName});
}
const canvas = createCanvas(0, 0);
const ctx = canvas.getContext("2d");
const max = {
left: 0,
right: 0,
ascent: 0,
descent: 0
};
let lastDescent;
const lineProps = text.split("\n").map(line => {
ctx.font = options.font;
const metrics = ctx.measureText(line);
const left = -1 * metrics.actualBoundingBoxLeft;
const right = metrics.actualBoundingBoxRight;
const ascent = metrics.actualBoundingBoxAscent;
const descent = metrics.actualBoundingBoxDescent;
max.left = Math.max(max.left, left);
max.right = Math.max(max.right, right);
max.ascent = Math.max(max.ascent, ascent);
max.descent = Math.max(max.descent, descent);
lastDescent = descent;
return {line, left, right, ascent, descent};
});
const lineHeight = max.ascent + max.descent + options.lineSpacing;
const contentWidth = max.left + max.right;
const contentHeight =
lineHeight * lineProps.length -
options.lineSpacing -
(max.descent - lastDescent);
canvas.width =
contentWidth +
options.borderLeftWidth +
options.borderRightWidth +
options.paddingLeft +
options.paddingRight;
canvas.height =
contentHeight +
options.borderTopWidth +
options.borderBottomWidth +
options.paddingTop +
options.paddingBottom;
const hasBorder =
false ||
options.borderLeftWidth ||
options.borderTopWidth ||
options.borderRightWidth ||
options.borderBottomWidth;
if (hasBorder) {
ctx.fillStyle = options.borderColor;
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
if (options.backgroundColor) {
ctx.fillStyle = options.backgroundColor;
ctx.fillRect(
options.borderLeftWidth,
options.borderTopWidth,
canvas.width - (options.borderLeftWidth + options.borderRightWidth),
canvas.height - (options.borderTopWidth + options.borderBottomWidth)
);
} else if (hasBorder) {
ctx.clearRect(
options.borderLeftWidth,
options.borderTopWidth,
canvas.width - (options.borderLeftWidth + options.borderRightWidth),
canvas.height - (options.borderTopWidth + options.borderBottomWidth)
);
}
ctx.font = options.font;
ctx.antialias = "gray";
ctx.textAlign = options.textAlign;
ctx.lineWidth = options.strokeWidth;
ctx.strokeStyle = options.strokeColor;
let offsetY = options.borderTopWidth + options.paddingTop;
lineProps.forEach(lineProp => {
// Calculate Y
let x = 0;
let y = max.ascent + offsetY;
// Calculate X
switch (options.textAlign) {
case "start":
case "left":
x = lineProp.left + options.borderLeftWidth + options.paddingLeft;
break;
case "end":
case "right":
x =
canvas.width -
lineProp.left -
options.borderRightWidth -
options.paddingRight;
break;
case "center":
x = contentWidth / 2 + options.borderLeftWidth + options.paddingLeft;
break;
}
let linePropIterator = 0;
let stylingChars = '{}';
let subtext = '';
let textColor = options.textColor;
while (linePropIterator < lineProp.line.length) {
let wordCharCode = lineProp.line.charCodeAt(linePropIterator);
let word = lineProp.line[linePropIterator];
if (wordCharCode < 256) {
if (stylingChars.indexOf(word) > -1) {
if (word === '{') {
textColor = lineProp.line.substr(linePropIterator + 1, 7);
linePropIterator += 7;
} else if (word === '}') {
textColor = options.textColor;
}
} else {
subtext += word;
}
if (subtext !== '') {
renderText(subtext);
subtext = '';
}
linePropIterator += 1;
}
}
function renderText(text) {
ctx.fillStyle = textColor;
ctx.fillText(text, x + randomInt(20, 50), y); // <-- Random int seems to change the spacing but how to calculate it?
if (options.strokeWidth > 0) {
ctx.strokeText(lineProp.line, x, y);
}
}
offsetY += lineHeight;
});
switch (options.output) {
case "buffer":
return canvas.toBuffer();
case "stream":
return canvas.createPNGStream();
case "dataURL":
return canvas.toDataURL("image/png");
case "canvas":
return canvas;
default:
throw new Error(`output type:${options.output} is not supported.`);
}
};
function parseOptions(options) {
return {
font: or(options.font, "30px sans-serif"),
textAlign: or(options.textAlign, "left"),
textColor: or(options.textColor, options.color, "black"),
backgroundColor: or(options.bgColor, options.backgroundColor, null),
lineSpacing: or(options.lineSpacing, 0),
letterSpacing: or(options.letterSpacing, 0),
strokeWidth: or(options.strokeWidth, 0),
strokeColor: or(options.strokeColor, "white"),
paddingLeft: or(options.paddingLeft, options.padding, 0),
paddingTop: or(options.paddingTop, options.padding, 0),
paddingRight: or(options.paddingRight, options.padding, 0),
paddingBottom: or(options.paddingBottom, options.padding, 0),
borderLeftWidth: or(options.borderLeftWidth, options.borderWidth, 0),
borderTopWidth: or(options.borderTopWidth, options.borderWidth, 0),
borderBottomWidth: or(options.borderBottomWidth, options.borderWidth, 0),
borderRightWidth: or(options.borderRightWidth, options.borderWidth, 0),
borderColor: or(options.borderColor, "black"),
localFontName: or(options.localFontName, null),
localFontPath: or(options.localFontPath, null),
output: or(options.output, "buffer")
};
}
function or() {
for (let arg of arguments) {
if (typeof arg !== "undefined") {
return arg;
}
}
return arguments[arguments.length - 1];
}
function randomInt(e, t) {
return Math.floor(Math.random() * (t - e + 1) + e)
}
module.exports = text2png;
I've tried to change the x value with a random int value which seems to be a part of the solution but how to calculate now the x value by respecting the letterSpacing option?
I couldn't quite get to the end of your code, since the snippet you contributed is rather comprehensive... But I believe what you're looking for is the measureText() Method of the canvas context. It returns the width the given String would have, if it were displayed on the canvas; you can read more about that here.
I guess you could use this method to calculate the offsets needed for the different letters. Since measureText() already considers the font type you won't have to worry about different char widths...
I'm currently working on a javascript animation that transitions details on an event action - however, I am having an issue removing a certain rogue div.
I have my init function as follows:
var demo = (function(window, undefined) {
function init() {
_mapPolygons(pattern);
_bindCards();
$(document.getElementsByClassName("card__overlay")).addClass(CLASSES.overlayHidden);
};
return {
init: init
};
})(window);
window.onload = demo.init;
The full snippet is here:
'use strict';
/**
* Demo.
*/
var demo = (function(window, undefined) {
/**
* Enum of CSS selectors.
*/
var SELECTORS = {
pattern: '.pattern',
card: '.card',
cardImage: '.card__image',
cardClose: '.card__btn-close',
};
/**
* Enum of CSS classes.
*/
var CLASSES = {
patternHidden: 'pattern--hidden',
polygon: 'polygon',
polygonHidden: 'polygon--hidden',
overlayHidden: 'overlay--hidden',
};
/**
* Map of svg paths and points.
*/
var polygonMap = {
paths: null,
points: null
};
/**
* Container of Card instances.
*/
var layout = {};
/**
* Initialise demo.
*/
function init() {
// For options see: https://github.com/qrohlf/Trianglify
var pattern = Trianglify({
width: window.innerWidth,
height: window.innerHeight,
cell_size: 90,
variance: 1,
stroke_width: 0.6,
color_function : function(x, y) {
return '#f0f3f5';
}
}).svg(); // Render as SVG.
_mapPolygons(pattern);
_bindCards();
$(document.getElementsByClassName("card__overlay")).addClass(CLASSES.overlayHidden);
};
/**
* Store path elements, map coordinates and sizes.
* #param {Element} pattern The SVG Element generated with Trianglify.
* #private
*/
function _mapPolygons(pattern) {
// Append SVG to pattern container.
$(SELECTORS.pattern).append(pattern);
// Convert nodelist to array,
// Used `.childNodes` because IE doesn't support `.children` on SVG.
polygonMap.paths = [].slice.call(pattern.childNodes);
polygonMap.points = [];
polygonMap.paths.forEach(function(polygon) {
// Hide polygons by adding CSS classes to each svg path (used attrs because of IE).
$(polygon).attr('class', CLASSES.polygon + ' ' + CLASSES.polygonHidden);
var rect = polygon.getBoundingClientRect();
var point = {
x: rect.left + rect.width / 2,
y: rect.top + rect.height / 2
};
polygonMap.points.push(point);
});
// All polygons are hidden now, display the pattern container.
$(SELECTORS.pattern).removeClass(CLASSES.patternHidden);
};
/**
* Bind Card elements.
* #private
*/
function _bindCards() {
var elements = $(SELECTORS.card);
$.each(elements, function(card, i) {
var instance = new Card(i, card);
layout[i] = {
card: instance
};
var cardImage = $(card).find(SELECTORS.cardImage);
var cardClose = $(card).find(SELECTORS.cardClose);
$(cardImage).on('click', _playSequence.bind(this, true, i));
$(cardClose).on('click', _playSequence.bind(this, false, i));
});
};
/**
* Create a sequence for the open or close animation and play.
* #param {boolean} isOpenClick Flag to detect when it's a click to open.
* #param {number} id The id of the clicked card.
* #param {Event} e The event object.
* #private
*
*/
function _playSequence(isOpenClick, id, e) {
var card = layout[id].card;
// Prevent when card already open and user click on image.
if (card.isOpen && isOpenClick) return;
// Create timeline for the whole sequence.
var sequence = new TimelineLite({paused: true});
var tweenOtherCards = _showHideOtherCards(id);
if (!card.isOpen) {
// Open sequence.
sequence.add(tweenOtherCards);
sequence.add(card.openCard(_onCardMove), 0);
} else {
// Close sequence.
var closeCard = card.closeCard();
var position = closeCard.duration() * 0.8; // 80% of close card tween.
sequence.add(closeCard);
sequence.add(tweenOtherCards, position);
}
sequence.play();
};
/**
* Show/Hide all other cards.
* #param {number} id The id of the clcked card to be avoided.
* #private
*/
function _showHideOtherCards(id) {
var TL = new TimelineLite;
var selectedCard = layout[id].card;
for (var i in layout) {
var card = layout[i].card;
// When called with `openCard`.
if (card.id !== id && !selectedCard.isOpen) {
TL.add(card.hideCard(), 0);
}
// When called with `closeCard`.
if (card.id !== id && selectedCard.isOpen) {
TL.add(card.showCard(), 0);
}
}
return TL;
};
/**
* Callback to be executed on Tween update, whatever a polygon
* falls into a circular area defined by the card width the path's
* CSS class will change accordingly.
* #param {Object} track The card sizes and position during the floating.
* #private
*/
function _onCardMove(track) {
var radius = track.width / 2;
var center = {
x: track.x,
y: track.y
};
polygonMap.points.forEach(function(point, i) {
if (_detectPointInCircle(point, radius, center)) {
$(polygonMap.paths[i]).attr('class', CLASSES.polygon);
} else {
$(polygonMap.paths[i]).attr('class', CLASSES.polygon + ' ' + CLASSES.polygonHidden);
}
});
}
/**
* Detect if a point is inside a circle area.
* #private
*/
function _detectPointInCircle(point, radius, center) {
var xp = point.x;
var yp = point.y;
var xc = center.x;
var yc = center.y;
var d = radius * radius;
var isInside = Math.pow(xp - xc, 2) + Math.pow(yp - yc, 2) <= d;
return isInside;
};
// Expose methods.
return {
init: init
};
})(window);
// Kickstart Demo.
window.onload = demo.init;
)
I'm essentially adding a class which should completely hide a div element, for which I have overkilled due in part to major frustrations:
.overlay--hidden {
display: none !important;
overflow: hidden !important;
opacity: 0 !important;
}
So this should completely kill the card__overlay div. I have added the same css "stylings" within the inspector and it works as should.
<div class="card__content card__overlay">
<div class="card__caption">
<p class="card__subtitle overlay">NEWS</p>
</div>
</div>
My issue is that essentially the getElementsByClassName method in the init function doesn't seem to be working - the class I want which is essentially hiding the div is not being added!
Is it possible to create two layers (with one being translucent) in OpenLayers and move them independently? If so, how?
I want to let the user choose which layer to move or if that's not possible, move one layer via my own JavaScript code while the other is controlled by the user.
Both will be prerendered pixmap layers, if that is important.
This is the solution I came up with. It isn't pretty but it works for my purposes.
Better alternatives are very welcome ...
/**
* #requires OpenLayers/Layer/TMS.js
*/
MyLayer = OpenLayers.Class(OpenLayers.Layer.TMS, {
latShift: 0.0,
latShiftPx: 0,
setMap: function(map) {
OpenLayers.Layer.TMS.prototype.setMap.apply(this, arguments);
map.events.register("moveend", this, this.mapMoveEvent)
},
// This is the function you will want to modify for your needs
mapMoveEvent: function(event) {
var resolution = this.map.getResolution();
var center = this.map.getCenter();
// This is some calculation I use, replace it whatever you like:
var h = center.clone().transform(projmerc, proj4326);
var elliptical = EllipticalMercator.fromLonLat(h.lon, h.lat);
var myCenter = new OpenLayers.LonLat(elliptical.x, elliptical.y);
this.latShift = myCenter.lat - center.lat;
this.latShiftPx = Math.round(this.latShift/resolution);
this.div.style.top = this.latShiftPx + "px";
},
moveTo: function(bounds, zoomChanged, dragging) {
bounds = bounds.add(0, this.latShift);
OpenLayers.Layer.TMS.prototype.moveTo.apply(this, [bounds, zoomChanged, dragging]);
},
// mostly copied and pasted from Grid.js ...
moveGriddedTiles: function() {
var buffer = this.buffer + 1;
while(true) {
var tlTile = this.grid[0][0];
var tlViewPort = {
x: tlTile.position.x +
this.map.layerContainerOriginPx.x,
y: tlTile.position.y +
this.map.layerContainerOriginPx.y + this.latShiftPx // ... except this line
};
var ratio = this.getServerResolution() / this.map.getResolution();
var tileSize = {
w: Math.round(this.tileSize.w * ratio),
h: Math.round(this.tileSize.h * ratio)
};
if (tlViewPort.x > -tileSize.w * (buffer - 1)) {
this.shiftColumn(true, tileSize);
} else if (tlViewPort.x < -tileSize.w * buffer) {
this.shiftColumn(false, tileSize);
} else if (tlViewPort.y > -tileSize.h * (buffer - 1)) {
this.shiftRow(true, tileSize);
} else if (tlViewPort.y < -tileSize.h * buffer) {
this.shiftRow(false, tileSize);
} else {
break;
}
}
},
CLASS_NAME: "MyLayer"
});
Note that this only works with OpenLayers 2.13 or newer
I am using JavaScript and trying to make a skew effect on a div.
First, take a look at this video: http://www.youtube.com/watch?v=ny5Uy81smpE (0:40-0:60 should be enough). The video shows some nice transformations (skew) when you move the window. What I want to do is the same thing: to skew a div when I move it.
Currently I just have a plain simple div:
<div id="a" style="background: #0f0; position: absolute; left: 0px; top: 0px;"></div>
I have done a simple skew transformation using the CSS3's transform property, but my implementation is buggy. Are there good tutorials or maths sites or resources that describe the logic behind this? I know JavaScript and CSS well enough to implement, if I just knew the logic and maths. I tried reading FreeWins source code, but I am not good in C.
I am accepting any resourceful answers or pseudo code. My dragging system is part of a bigger system, thus, now that I post some real code, it does not work without giving you the entire system (that I can not do at this point). So, you can't run this code as is. The code I use is this (slightly modified though) to demonstrate my idea:
/**
* The draggable object.
*/
Draggable = function(targetElement, options) {
this.targetElement = targetElement;
// Initialize drag data.
this.dragData = {
startX: null,
startY: null,
lastX: null,
lastY: null,
offsetX: null,
offsetY: null,
lastTime: null,
occuring: false
};
// Set the cursor style.
targetElement.style.cursor = 'move';
// The element to move.
this.applyTo = options.applyTo || targetElement;
// Event methods for "mouse down", "up" and "move".
// Mouse up and move are binded to window.
// We can attach and deattach "move" and "up" events as needed.
var me = this;
targetElement.addEventListener('mousedown', function(event) {
me.onMouseDown.call(me, event);
}, false);
this.mouseUp = function(event) {
me.onMouseUp.call(me, event);
};
this.mouseMove = function(event) {
me.onMouseMove.call(me, event);
};
};
/**
* The mouse down event.
* #param {Object} event
*/
Draggable.prototype.onMouseDown = function(event) {
// New drag event.
if (this.dragData.occuring === false) {
this.dragData.occuring = true;
this.dragData.startX = this.dragData.lastX = event.clientX;
this.dragData.startY = this.dragData.lastY = event.clientY;
this.dragData.offsetX = parseInt(this.applyTo.style.left, 10) - event.clientX;
this.dragData.offsetY = parseInt(this.applyTo.style.top, 10) - event.clientY;
this.dragData.lastTime = (new Date()).getTime();
// Mouse up and move events.
var me = this;
window.addEventListener('mousemove', this.mouseMove, false);
window.addEventListener('mouseup', this.mouseUp, false);
}
};
/**
* The mouse movement event.
* #param {Object} event
*/
Draggable.prototype.onMouseMove = function(event) {
if (this.dragData.occuring === true) {
// He is dragging me now, we move if there is need for that.
var moved = (this.dragData.lastX !== event.clientX || this.dragData.lastY !== event.clientY);
if (moved === true) {
var element = this.applyTo;
// The skew animation. :)
var skew = (this.dragData.lastX - event.clientX) * 1;
var limit = 25;
if (Math.abs(skew) > limit) {
skew = limit * (skew > 0 ? 1 : -1);
}
var transform = 'translateX(' + (event.clientX + this.dragData.offsetX - parseInt(element.style.left, 10)) + 'px)';
transform += 'translateY(' + (event.clientY + this.dragData.offsetY - parseInt(element.style.top, 10)) + 'px)';
transform += 'skew(' + skew + 'deg)';
element.style.MozTransform = transform;
element.style.webkitTransform = transform;
this.dragData.lastX = event.clientX;
this.dragData.lastY = event.clientY;
this.dragData.lastTime = (new Date()).getTime();
}
}
};
/**
* The mouse up event.
* #param {Object} event
*/
Draggable.prototype.onMouseUp = function(event) {
this.dragData.occuring = false;
var element = this.applyTo;
// Reset transformations.
element.style.MozTransform = '';
element.style.webkitTransform = '';
// Save the new position.
element.style.left = (this.dragData.lastX + this.dragData.offsetX) + 'px';
element.style.top = (this.dragData.lastY + this.dragData.offsetY) + 'px';
// Remove useless events.
window.removeEventListener('mousemove', this.mouseMove, false);
window.removeEventListener('mousemove', this.mouseUp, false);
};
Currently my dragging system is buggy and simple. I need more information on the logic that I should be applying.
Wow, the idea rocks. :) I've cleaned your code a bit, and solved the problems with initialization. Now it works fine for me on Firefox and Chrome (even though you said it shouldn't).
A few notes:
you need to grab the starting top and left positions during initialization (getBoundingClientRect)
store references like this.dragData and element.style for shortness and faster execution
dragData can be initialized as an empty object. It's fine in javascript. You can add properties later.
options should be conditionally initialized as an empty object, so that you can take zero options
moved and dragData.occuring were totally useless because of the event management
preventDefault is needed in order not to select text during dragging
you may want to keep track of z-indexes to be the active element always visible
Have fun!
Code [See it in action]
/**
* The draggable object.
*/
Draggable = function(targetElement, options) {
this.targetElement = targetElement;
// we can take zero options
options = options || {};
// Initialize drag data.
// #props: startX, startY, lastX, lastY,
// offsetX, offsetY, lastTime, occuring
this.dragData = {};
// Set the cursor style.
targetElement.style.cursor = 'move';
// The element to move.
var el = this.applyTo = options.applyTo || targetElement;
// Event methods for "mouse down", "up" and "move".
// Mouse up and move are binded to window.
// We can attach and deattach "move" and "up" events as needed.
var me = this;
targetElement.addEventListener('mousedown', function(event) {
me.onMouseDown.call(me, event);
}, false);
this.mouseUp = function(event) {
me.onMouseUp.call(me, event);
};
this.mouseMove = function(event) {
me.onMouseMove.call(me, event);
};
// initialize position, so it will
// be smooth even on the first drag
var position = el.getBoundingClientRect();
el.style.left = position.left + "px";
el.style.top = position.top + "px";
el.style.position = "absolute";
if (el.style.zIndex > Draggable.zindex)
Draggable.zindex = el.style.zIndex + 1;
};
Draggable.zindex = 0;
/**
* Sets the skew and saves the position
* #param {Number} skew
*/
Draggable.prototype.setSkew = function(skew) {
var data = this.dragData;
var style = this.applyTo.style;
// Set skew transformations.
data.skew = skew;
style.MozTransform = skew ? 'skew(' + skew + 'deg)' : '';
style.webkitTransform = skew ? 'skew(' + skew + 'deg)' : '';
// Save the new position.
style.left = (data.lastX + data.offsetX) + 'px';
style.top = (data.lastY + data.offsetY) + 'px';
}
/**
* The mouse down event.
* #param {Object} event
*/
Draggable.prototype.onMouseDown = function(event) {
var data = this.dragData;
// New drag event.
var style = this.applyTo.style;
data.startX = data.lastX = event.clientX;
data.startY = data.lastY = event.clientY;
data.offsetX = parseInt(style.left, 10) - event.clientX;
data.offsetY = parseInt(style.top, 10) - event.clientY;
style.zIndex = Draggable.zindex++;
data.lastTime = (new Date()).getTime();
// Mouse up and move events.
window.addEventListener('mousemove', this.mouseMove, false);
window.addEventListener('mouseup', this.mouseUp, false);
event.preventDefault(); // prevent text selection
};
/**
* The mouse movement event.
* #param {Object} event
*/
Draggable.prototype.onMouseMove = function(event) {
// He is dragging me now
var me = this;
var data = me.dragData;
var element = me.applyTo;
var clientX = event.clientX;
var clientY = event.clientY;
data.moving = true;
// The skew animation. :)
var skew = (data.lastX - clientX) * 1;
var limit = 25;
if (Math.abs(skew) > limit) {
skew = limit * (skew > 0 ? 1 : -1);
}
var style = element.style;
var left = parseInt(style.left, 10);
var top = parseInt(style.top, 10);
var transform =
'translateX(' + (clientX + data.offsetX - left) + 'px)' +
'translateY(' + (clientY + data.offsetY - top) + 'px)' +
'skew(' + skew + 'deg)';
style.MozTransform = transform;
style.webkitTransform = transform;
data.lastX = clientX;
data.lastY = clientY;
data.lastTime = (new Date()).getTime();
// here is the cooldown part in order
// not to stay in disorted state
var pre = skew > 0 ? 1 : -1;
clearInterval(data.timer);
data.timer = setInterval(function() {
var skew = data.skew - (pre * 10);
skew = pre * skew < 0 ? 0 : skew;
me.setSkew(skew);
if (data.moving || skew === 0)
clearInterval(data.timer);
}, 20);
data.moving = false;
};
/**
* The mouse up event.
* #param {Object} event
*/
Draggable.prototype.onMouseUp = function(event) {
this.setSkew('');
// Remove useless events.
window.removeEventListener('mousemove', this.mouseMove, false);
window.removeEventListener('mousemove', this.mouseUp, false);
};