Drawing rectangles on top of image R shiny - javascript

I'd like to elaborate on the accepted answer to this question.
I'm looking at improving the minimal shiny app below (extracted from the accepted answer) with the following features:
1) draw the rectangle + a text label. The label comes from R (input$foo), e.g., from a dropdown. To avoid the edge cases where the labels fall outside the images, labels should be placed inside their rectangles.
2) use a different color for the rectangles and their labels depending on the label
3) ability for the user to delete a rectangle by double-clicking inside it. In the case of multiple matches (overlap, nested), the rectangle with the smallest area should be deleted.
Brownie points for 1): the dropdown could appear next to the cursor like is done here (code here). If possible, the dropdown list should be passed from server.R and not be fixed/hardcoded. The reason is that depending on some user input, a different dropdown could be shown. E.g., we might have one dropdown for fruits c('banana','pineapple','grapefruit'), one dropdown for animals c('raccoon','dog','cat'), etc.
# JS and CSS modified from: https://stackoverflow.com/a/17409472/8099834
css <- "
#canvas {
width:2000px;
height:2000px;
border: 10px solid transparent;
}
.rectangle {
border: 5px solid #FFFF00;
position: absolute;
}
"
js <-
"function initDraw(canvas) {
var mouse = {
x: 0,
y: 0,
startX: 0,
startY: 0
};
function setMousePosition(e) {
var ev = e || window.event; //Moz || IE
if (ev.pageX) { //Moz
mouse.x = ev.pageX + window.pageXOffset;
mouse.y = ev.pageY + window.pageYOffset;
} else if (ev.clientX) { //IE
mouse.x = ev.clientX + document.body.scrollLeft;
mouse.y = ev.clientY + document.body.scrollTop;
}
};
var element = null;
canvas.onmousemove = function (e) {
setMousePosition(e);
if (element !== null) {
element.style.width = Math.abs(mouse.x - mouse.startX) + 'px';
element.style.height = Math.abs(mouse.y - mouse.startY) + 'px';
element.style.left = (mouse.x - mouse.startX < 0) ? mouse.x + 'px' : mouse.startX + 'px';
element.style.top = (mouse.y - mouse.startY < 0) ? mouse.y + 'px' : mouse.startY + 'px';
}
}
canvas.onclick = function (e) {
if (element !== null) {
var coord = {
left: element.style.left,
top: element.style.top,
width: element.style.width,
height: element.style.height
};
Shiny.onInputChange('rectCoord', coord);
element = null;
canvas.style.cursor = \"default\";
} else {
mouse.startX = mouse.x;
mouse.startY = mouse.y;
element = document.createElement('div');
element.className = 'rectangle'
element.style.left = mouse.x + 'px';
element.style.top = mouse.y + 'px';
canvas.appendChild(element);
canvas.style.cursor = \"crosshair\";
}
}
};
$(document).on('shiny:sessioninitialized', function(event) {
initDraw(document.getElementById('canvas'));
});
"
library(shiny)
ui <- fluidPage(
tags$head(
tags$style(css),
tags$script(HTML(js))
),
fluidRow(
column(width = 6,
# inline is necessary
# ...otherwise we can draw rectangles over entire fluidRow
uiOutput("canvas", inline = TRUE)),
column(
width = 6,
verbatimTextOutput("rectCoordOutput")
)
)
)
server <- function(input, output, session) {
output$canvas <- renderUI({
tags$img(src = "https://www.r-project.org/logo/Rlogo.png")
})
output$rectCoordOutput <- renderPrint({
input$rectCoord
})
}
shinyApp(ui, server)

This solution uses kyamagu's bbox_annotator and is based on demo.html. I'm not familiar with JS, so it's not the prettiest. Limitations are:
Choosing a different image url will remove previous rectangles
I edited the JS a bit to change the rectangle/text color, so you won't be able to pull directly from the original repo
My changes probably broke input_method = "fixed" and "text", I only tested input_method = "select"
ui.R
# Adapted from https://github.com/kyamagu/bbox-annotator/
# Edited original JS to add color_list as an option
# ...should be the same length as labels
# ...and controls the color of the rectangle
# ...will probably be broken for input_method = "fixed" or "text"
# Also added color as a value in each rectangle entry
js <- '
$(document).ready(function() {
// define options to pass to bounding box constructor
var options = {
url: "https://www.r-project.org/logo/Rlogo.svg",
input_method: "select",
labels: [""],
color_list: [""],
onchange: function(entries) {
Shiny.onInputChange("rectCoord", JSON.stringify(entries, null, " "));
}
};
// Initialize the bounding-box annotator.
var annotator = new BBoxAnnotator(options);
// Initialize the reset button.
$("#reset_button").click(function(e) {
annotator.clear_all();
})
// define function to reset the bbox
// ...upon choosing new label category or new url
function reset_bbox(options) {
document.getElementById("bbox_annotator").setAttribute("style", "display:inline-block");
$(".image_frame").remove();
annotator = new BBoxAnnotator(options);
}
// update image url from shiny
Shiny.addCustomMessageHandler("change-img-url", function(url) {
options.url = url;
options.width = null;
options.height = null;
reset_bbox(options);
});
// update colors and categories from shiny
Shiny.addCustomMessageHandler("update-category-list", function(vals) {
options.labels = Object.values(vals);
options.color_list = Object.keys(vals);
reset_bbox(options);
});
// redraw rectangles based on list of entries
Shiny.addCustomMessageHandler("redraw-rects", function(vals) {
var arr = JSON.parse(vals);
arr.forEach(function(rect){
annotator.add_entry(rect);
});
if (annotator.onchange) {
annotator.onchange(annotator.entries);
}
});
});
'
ui <- fluidPage(
tags$head(tags$script(HTML(js)),
tags$head(
tags$script(src = "bbox_annotation.js")
)),
titlePanel("Bounding box annotator demo"),
sidebarLayout(
sidebarPanel(
selectInput(
"img_url",
"URLs",
c(
"https://www.r-project.org/logo/Rlogo.svg",
"https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png"
)
),
selectInput("category_type", "Label Category", c("animals", "fruits")),
div(HTML(
'<input id="reset_button" type="reset" />'
)),
HTML(
'<input id="annotation_data" name="annotation_data" type="hidden" />'
),
hr(),
h4("Entries"),
verbatimTextOutput("rectCoordOutput")
),
mainPanel(div(id = "bbox_annotator", style = "display:inline-block"))
)
)
server.R
server <- function(input, output, session) {
# user choices
output$rectCoordOutput <- renderPrint({
if(!is.null(input$rectCoord)) {
as.data.frame(jsonlite::fromJSON(input$rectCoord))
}
})
# send chosen URL from shiny to JS
observeEvent(input$img_url, {
session$sendCustomMessage("change-img-url", input$img_url)
})
# send chosen category list from shiny to JS
observeEvent(input$category_type, {
vals <- switch(input$category_type,
fruits = list("yellow" = "banana",
"orange" = "pineapple",
"pink" = "grapefruit"),
animals = list("grey" = "raccoon",
"brown" = "dog",
"tan" = "cat")
)
# update category list
session$sendCustomMessage("update-category-list", vals)
# redraw rectangles
session$sendCustomMessage("redraw-rects", input$rectCoord)
})
}
www/bbox_annotation.js
// Generated by CoffeeScript 2.5.0
(function() {
// https://github.com/kyamagu/bbox-annotator/blob/master/bbox_annotator.coffee
// Use coffee-script compiler to obtain a javascript file.
// coffee -c bbox_annotator.coffee
// See http://coffeescript.org/
// BBox selection window.
var BBoxSelector;
BBoxSelector = class BBoxSelector {
// Initializes selector in the image frame.
constructor(image_frame, options) {
if (options == null) {
options = {};
}
options.input_method || (options.input_method = "text");
this.image_frame = image_frame;
this.border_width = options.border_width || 2;
this.selector = $('<div class="bbox_selector"></div>');
this.selector.css({
// rectangle color when dragging
"border": this.border_width + "px dotted rgb(127,255,127)",
"position": "absolute"
});
this.image_frame.append(this.selector);
this.selector.css({
"border-width": this.border_width
});
this.selector.hide();
this.create_label_box(options);
}
// Initializes a label input box.
create_label_box(options) {
var i, label, len, ref;
options.labels || (options.labels = ["object"]);
this.label_box = $('<div class="label_box" style="z-index: 1000"></div>');
this.label_box.css({
"position": "absolute"
});
this.image_frame.append(this.label_box);
switch (options.input_method) {
case 'select':
if (typeof options.labels === "string") {
options.labels = [options.labels];
}
this.label_input = $('<select class="label_input" name="label"></select>');
this.label_box.append(this.label_input);
this.label_input.append($('<option value>choose an item</option>'));
ref = options.labels;
for (i = 0, len = ref.length; i < len; i++) {
label = ref[i];
this.label_input.append('<option value="' + label + '">' + label + '</option>');
}
this.label_input.change(function(e) {
return this.blur();
});
break;
case 'text':
if (typeof options.labels === "string") {
options.labels = [options.labels];
}
this.label_input = $('<input class="label_input" name="label" ' + 'type="text" value>');
this.label_box.append(this.label_input);
this.label_input.autocomplete({
source: options.labels || [''],
autoFocus: true
});
break;
case 'fixed':
if ($.isArray(options.labels)) {
options.labels = options.labels[0];
}
this.label_input = $('<input class="label_input" name="label" type="text">');
this.label_box.append(this.label_input);
this.label_input.val(options.labels);
break;
default:
throw 'Invalid label_input parameter: ' + options.input_method;
}
return this.label_box.hide();
}
// Crop x and y to the image size.
crop(pageX, pageY) {
var point;
return point = {
x: Math.min(Math.max(Math.round(pageX - this.image_frame.offset().left), 0), Math.round(this.image_frame.width() - 1)),
y: Math.min(Math.max(Math.round(pageY - this.image_frame.offset().top), 0), Math.round(this.image_frame.height() - 1))
};
}
// When a new selection is made.
start(pageX, pageY) {
this.pointer = this.crop(pageX, pageY);
this.offset = this.pointer;
this.refresh();
this.selector.show();
$('body').css('cursor', 'crosshair');
return document.onselectstart = function() {
return false;
};
}
// When a selection updates.
update_rectangle(pageX, pageY) {
this.pointer = this.crop(pageX, pageY);
return this.refresh();
}
// When starting to input label.
input_label(options) {
$('body').css('cursor', 'default');
document.onselectstart = function() {
return true;
};
this.label_box.show();
return this.label_input.focus();
}
// Finish and return the annotation.
finish(options) {
var data;
this.label_box.hide();
this.selector.hide();
data = this.rectangle();
data.label = $.trim(this.label_input.val().toLowerCase());
if (options.input_method !== 'fixed') {
this.label_input.val('');
}
return data;
}
// Get a rectangle.
rectangle() {
var rect, x1, x2, y1, y2;
x1 = Math.min(this.offset.x, this.pointer.x);
y1 = Math.min(this.offset.y, this.pointer.y);
x2 = Math.max(this.offset.x, this.pointer.x);
y2 = Math.max(this.offset.y, this.pointer.y);
return rect = {
left: x1,
top: y1,
width: x2 - x1 + 1,
height: y2 - y1 + 1
};
}
// Update css of the box.
refresh() {
var rect;
rect = this.rectangle();
this.selector.css({
left: (rect.left - this.border_width) + 'px',
top: (rect.top - this.border_width) + 'px',
width: rect.width + 'px',
height: rect.height + 'px'
});
return this.label_box.css({
left: (rect.left - this.border_width) + 'px',
top: (rect.top + rect.height + this.border_width) + 'px'
});
}
// Return input element.
get_input_element() {
return this.label_input;
}
};
// Annotator object definition.
this.BBoxAnnotator = class BBoxAnnotator {
// Initialize the annotator layout and events.
constructor(options) {
var annotator, image_element;
annotator = this;
this.annotator_element = $(options.id || "#bbox_annotator");
// allow us to access colors and labels in future steps
this.color_list = options.color_list;
this.label_list = options.labels;
this.border_width = options.border_width || 2;
this.show_label = options.show_label || (options.input_method !== "fixed");
if (options.multiple != null) {
this.multiple = options.multiple;
} else {
this.multiple = true;
}
this.image_frame = $('<div class="image_frame"></div>');
this.annotator_element.append(this.image_frame);
if (options.guide) {
annotator.initialize_guide(options.guide);
}
image_element = new Image();
image_element.src = options.url;
image_element.onload = function() {
options.width || (options.width = image_element.width);
options.height || (options.height = image_element.height);
annotator.annotator_element.css({
"width": (options.width + annotator.border_width) + 'px',
"height": (options.height + annotator.border_width) + 'px',
"padding-left": (annotator.border_width / 2) + 'px',
"padding-top": (annotator.border_width / 2) + 'px',
"cursor": "crosshair",
"overflow": "hidden"
});
annotator.image_frame.css({
"background-image": "url('" + image_element.src + "')",
"width": options.width + "px",
"height": options.height + "px",
"position": "relative"
});
annotator.selector = new BBoxSelector(annotator.image_frame, options);
return annotator.initialize_events(options);
};
image_element.onerror = function() {
return annotator.annotator_element.text("Invalid image URL: " + options.url);
};
this.entries = [];
this.onchange = options.onchange;
}
// Initialize events.
initialize_events(options) {
var annotator, selector, status;
status = 'free';
this.hit_menuitem = false;
annotator = this;
selector = annotator.selector;
this.annotator_element.mousedown(function(e) {
if (!annotator.hit_menuitem) {
switch (status) {
case 'free':
case 'input':
if (status === 'input') {
selector.get_input_element().blur();
}
if (e.which === 1) { // left button
selector.start(e.pageX, e.pageY);
status = 'hold';
}
}
}
annotator.hit_menuitem = false;
return true;
});
$(window).mousemove(function(e) {
var offset;
switch (status) {
case 'hold':
selector.update_rectangle(e.pageX, e.pageY);
}
if (annotator.guide_h) {
offset = annotator.image_frame.offset();
annotator.guide_h.css('top', Math.floor(e.pageY - offset.top) + 'px');
annotator.guide_v.css('left', Math.floor(e.pageX - offset.left) + 'px');
}
return true;
});
$(window).mouseup(function(e) {
switch (status) {
case 'hold':
selector.update_rectangle(e.pageX, e.pageY);
selector.input_label(options);
status = 'input';
if (options.input_method === 'fixed') {
selector.get_input_element().blur();
}
}
return true;
});
selector.get_input_element().blur(function(e) {
var data;
switch (status) {
case 'input':
data = selector.finish(options);
if (data.label) {
// store color with the entry
// ...so we can redraw the rectangle upon changing label category
data.color = annotator.color_list[annotator.label_list.indexOf(data.label)];
annotator.add_entry(data);
if (annotator.onchange) {
annotator.onchange(annotator.entries);
}
}
status = 'free';
}
return true;
});
selector.get_input_element().keypress(function(e) {
switch (status) {
case 'input':
if (e.which === 13) {
selector.get_input_element().blur();
}
}
return e.which !== 13;
});
selector.get_input_element().mousedown(function(e) {
return annotator.hit_menuitem = true;
});
selector.get_input_element().mousemove(function(e) {
return annotator.hit_menuitem = true;
});
selector.get_input_element().mouseup(function(e) {
return annotator.hit_menuitem = true;
});
return selector.get_input_element().parent().mousedown(function(e) {
return annotator.hit_menuitem = true;
});
}
// Add a new entry.
add_entry(entry) {
var annotator, box_element, close_button, text_box;
if (!this.multiple) {
this.annotator_element.find(".annotated_bounding_box").detach();
this.entries.splice(0);
}
this.entries.push(entry);
box_element = $('<div class="annotated_bounding_box"></div>');
box_element.appendTo(this.image_frame).css({
// rectangle color -- when stopped dragging
"border": this.border_width + "px solid " + entry.color,
"position": "absolute",
"top": (entry.top - this.border_width) + "px",
"left": (entry.left - this.border_width) + "px",
"width": entry.width + "px",
"height": entry.height + "px",
// text color when stopped dragging
"color": entry.color,
"font-family": "monospace",
"font-size": "small"
});
close_button = $('<div></div>').appendTo(box_element).css({
"position": "absolute",
"top": "-8px",
"right": "-8px",
"width": "16px",
"height": "0",
"padding": "16px 0 0 0",
"overflow": "hidden",
"color": "#fff",
"background-color": "#030",
"border": "2px solid #fff",
"-moz-border-radius": "18px",
"-webkit-border-radius": "18px",
"border-radius": "18px",
"cursor": "pointer",
"-moz-user-select": "none",
"-webkit-user-select": "none",
"user-select": "none",
"text-align": "center"
});
$("<div></div>").appendTo(close_button).html('×').css({
"display": "block",
"text-align": "center",
"width": "16px",
"position": "absolute",
"top": "-2px",
"left": "0",
"font-size": "16px",
"line-height": "16px",
"font-family": '"Helvetica Neue", Consolas, Verdana, Tahoma, Calibri, ' + 'Helvetica, Menlo, "Droid Sans", sans-serif'
});
text_box = $('<div></div>').appendTo(box_element).css({
"overflow": "hidden"
});
if (this.show_label) {
text_box.text(entry.label);
}
annotator = this;
box_element.hover((function(e) {
return close_button.show();
}), (function(e) {
return close_button.hide();
}));
close_button.mousedown(function(e) {
return annotator.hit_menuitem = true;
});
close_button.click(function(e) {
var clicked_box, index;
clicked_box = close_button.parent(".annotated_bounding_box");
index = clicked_box.prevAll(".annotated_bounding_box").length;
clicked_box.detach();
annotator.entries.splice(index, 1);
return annotator.onchange(annotator.entries);
});
return close_button.hide();
}
// Clear all entries.
clear_all(e) {
this.annotator_element.find(".annotated_bounding_box").detach();
this.entries.splice(0);
return this.onchange(this.entries);
}
// Add crosshair guide.
initialize_guide(options) {
this.guide_h = $('<div class="guide_h"></div>').appendTo(this.image_frame).css({
"border": "1px dotted " + (options.color || '#000'),
"height": "0",
"width": "100%",
"position": "absolute",
"top": "0",
"left": "0"
});
return this.guide_v = $('<div class="guide_v"></div>').appendTo(this.image_frame).css({
"border": "1px dotted " + (options.color || '#000'),
"height": "100%",
"width": "0",
"position": "absolute",
"top": "0",
"left": "0"
});
}
};
}).call(this);

Related

Rater.js - Half stars instead of full stars for progress

Hy there! I plan to use Rater.js in my project and so far I am happy with the library. There is only one issue I can't resolve alone. Pls. take a look at
https://jsfiddle.net/herbert_hinterberger/r1cgnpt3/.
At the very bottom of the script, I defined step_size: 1.
(".rating").rate();
//or for example
var options = {
max_value: 5,
step_size: 1,
}
Now I would expect that when I hover over the stars, only full stars are selected. But still, half stars are selected too. Any Idea what I a doing wrong?
In your example code, you are calling $(".rating").rate(); without options (line number 499).
After commenting it, you will end-up with something like below
/*
* A highly customizable rating widget that supports images, utf8 glyphs and other html elements!
* https://github.com/auxiliary/rater
*/
; (function ($, window) {
$.fn.textWidth = function () {
var html_calc = $('<span>' + $(this).html() + '</span>');
html_calc.css('font-size', $(this).css('font-size')).hide();
html_calc.prependTo('body');
var width = html_calc.width();
html_calc.remove();
if (width == 0) {
var total = 0;
$(this).eq(0).children().each(function () {
total += $(this).textWidth();
});
return total;
}
return width;
};
$.fn.textHeight = function () {
var html_calc = $('<span>' + $(this).html() + '</span>');
html_calc.css('font-size', $(this).css('font-size')).hide();
html_calc.prependTo('body');
var height = html_calc.height();
html_calc.remove();
return height;
};
/*
* IE8 doesn't support isArray!
*/
Array.isArray = function (obj) {
return Object.prototype.toString.call(obj) === "[object Array]";
};
/*
* Utf-32 isn't supported by default, so we have to use Utf-8 surrogates
*/
String.prototype.getCodePointLength = function () {
return this.length - this.split(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g).length + 1;
};
String.fromCodePoint = function () {
var chars = Array.prototype.slice.call(arguments);
for (var i = chars.length; i-- > 0;) {
var n = chars[i] - 0x10000;
if (n >= 0)
chars.splice(i, 1, 0xD800 + (n >> 10), 0xDC00 + (n & 0x3FF));
}
return String.fromCharCode.apply(null, chars);
};
/*
* Starting the plugin itself
*/
$.fn.rate = function (options) {
if (options === undefined || typeof options === 'object') {
return this.each(function () {
if (!$.data(this, "rate")) {
$.data(this, "rate", new Rate(this, options));
}
});
}
else if (typeof options === 'string') {
var args = arguments;
var returns;
this.each(function () {
var instance = $.data(this, "rate");
if (instance instanceof Rate && typeof instance[options] === 'function') {
returns = instance[options].apply(instance, Array.prototype.slice.call(args, 1));
}
if (options === 'destroy') {
// Unbind all events and empty the plugin data from instance
$(instance.element).off();
$.data(this, 'rate', null);
}
});
return returns !== undefined ? returns : this;
}
};
function Rate(element, options) {
this.element = element;
this.settings = $.extend({}, $.fn.rate.settings, options);
this.set_faces = {}; // value, symbol pairs
this.build();
}
Rate.prototype.build = function () {
this.layers = {};
this.value = 0;
this.raise_select_layer = false;
if (this.settings.initial_value) {
this.value = this.settings.initial_value;
}
if ($(this.element).attr("data-rate-value")) {
this.value = $(this.element).attr("data-rate-value");
}
/*
* Calculate the selected width based on the initial value
*/
var selected_width = this.value / this.settings.max_value * 100;
/*
* Let's support single strings as symbols as well as objects
*/
if (typeof this.settings.symbols[this.settings.selected_symbol_type] === 'string') {
var symbol = this.settings.symbols[this.settings.selected_symbol_type];
this.settings.symbols[this.settings.selected_symbol_type] = {};
this.settings.symbols[this.settings.selected_symbol_type]['base'] = symbol;
this.settings.symbols[this.settings.selected_symbol_type]['selected'] = symbol;
this.settings.symbols[this.settings.selected_symbol_type]['hover'] = symbol;
}
/*
* Making the three main layers (base, select, hover)
*/
var base_layer = this.addLayer("base-layer", 100, this.settings.symbols[
this.settings.selected_symbol_type]["base"], true);
var select_layer = this.addLayer("select-layer", selected_width,
this.settings.symbols[this.settings.selected_symbol_type]["selected"], true);
var hover_layer = this.addLayer("hover-layer", 0, this.settings.symbols[
this.settings.selected_symbol_type]["hover"], false);
/* var face_layer = this.addLayer("face-layer", 1, this.settings
.symbols[this.settings.face_layer_symbol_type][0], true); */
this.layers["base_layer"] = base_layer;
this.layers["select_layer"] = select_layer;
this.layers["hover_layer"] = hover_layer;
/*
* Bind the container to some events
*/
$(this.element).on("mousemove", $.proxy(this.hover, this));
$(this.element).on("click", $.proxy(this.select, this));
$(this.element).on("mouseleave", $.proxy(this.mouseout, this));
/*
* Set the main element as unselectable
*/
$(this.element).css({
"-webkit-touch-callout": "none",
"-webkit-user-select": "none",
"-khtml-user-select": "none",
"-moz-user-select": "none",
"-ms-user-select": "none",
"user-select": "none",
});
/*
* Update custom input field if provided
*/
if (this.settings.hasOwnProperty("update_input_field_name")) {
this.settings.update_input_field_name.val(this.value);
}
}
/*
* Function to add a layer
*/
Rate.prototype.addLayer = function (layer_name, visible_width, symbol, visible) {
var layer_body = "<div>";
for (var i = 0; i < this.settings.max_value; i++) {
if (Array.isArray(symbol)) {
if (this.settings.convert_to_utf8) {
symbol[i] = String.fromCodePoint(symbol[i]);
}
layer_body += "<span>" + (symbol[i]) + "</span>";
}
else {
if (this.settings.convert_to_utf8) {
symbol = String.fromCodePoint(symbol);
}
layer_body += "<span>" + symbol + "</span>";
}
}
layer_body += "</div>";
var layer = $(layer_body).addClass("rate-" + layer_name).appendTo(this.element);
$(layer).css({
width: visible_width + "%",
height: $(layer).children().eq(0).textHeight(),
overflow: 'hidden',
position: 'absolute',
top: 0,
display: visible ? 'block' : 'none',
'white-space': 'nowrap'
});
$(this.element).css({
width: $(layer).textWidth() + "px",
height: $(layer).height(),
position: 'relative',
cursor: this.settings.cursor,
});
return layer;
}
Rate.prototype.updateServer = function () {
if (this.settings.url != undefined) {
$.ajax({
url: this.settings.url,
type: this.settings.ajax_method,
data: $.extend({}, { value: this.getValue() }, this.settings.additional_data),
success: $.proxy(function (data) {
$(this.element).trigger("updateSuccess", [data]);
}, this),
error: $.proxy(function (jxhr, msg, err) {
$(this.element).trigger("updateError", [jxhr, msg, err]);
}, this)
});
}
}
Rate.prototype.getValue = function () {
return this.value;
}
Rate.prototype.hover = function (ev) {
var pad = parseInt($(this.element).css("padding-left").replace("px", ""));
var x = ev.pageX - $(this.element).offset().left - pad;
var val = this.toValue(x, true);
if (val != this.value) {
this.raise_select_layer = false;
}
if (!this.raise_select_layer && !this.settings.readonly) {
var visible_width = this.toWidth(val);
this.layers.select_layer.css({ display: 'none' });
if (!this.settings.only_select_one_symbol) {
this.layers.hover_layer.css({
width: visible_width + "%",
display: 'block'
});
}
else {
var index_value = Math.floor(val);
this.layers.hover_layer.css({
width: "100%",
display: 'block'
});
this.layers.hover_layer.children("span").css({
visibility: 'hidden',
});
this.layers.hover_layer.children("span").eq(index_value != 0 ? index_value - 1 : 0).css({
visibility: 'visible',
});
}
}
}
/*
* Event for when a rating has been selected (clicked)
*/
Rate.prototype.select = function (ev) {
if (!this.settings.readonly) {
var old_value = this.getValue();
var pad = parseInt($(this.element).css("padding-left").replace("px", ""));
var x = ev.pageX - $(this.element).offset().left - pad;
var selected_width = this.toWidth(this.toValue(x, true));
this.setValue(this.toValue(selected_width));
this.raise_select_layer = true;
}
}
Rate.prototype.mouseout = function () {
this.layers.hover_layer.css({ display: 'none' });
this.layers.select_layer.css({ display: 'block' });
}
/*
* Takes a width (px) and returns the value it resembles
*/
Rate.prototype.toWidth = function (val) {
return val / this.settings.max_value * 100;
}
/*
* Takes a value and calculates the width of the selected/hovered layer
*/
Rate.prototype.toValue = function (width, in_pixels) {
var val;
if (in_pixels) {
val = width / this.layers.base_layer.textWidth() * this.settings.max_value;
}
else {
val = width / 100 * this.settings.max_value;
}
// Make sure the division doesn't cause some small numbers added by
// comparing to a small arbitrary number.
var temp = val / this.settings.step_size;
if (temp - Math.floor(temp) < 0.00005) {
val = Math.round(val / this.settings.step_size) * this.settings.step_size;
}
val = (Math.ceil(val / this.settings.step_size)) * this.settings.step_size;
val = val > this.settings.max_value ? this.settings.max_value : val;
return val;
}
Rate.prototype.getElement = function (layer_name, index) {
return $(this.element).find(".rate-" + layer_name + " span").eq(index - 1);
}
Rate.prototype.getLayers = function () {
return this.layers;
}
Rate.prototype.setFace = function (value, face) {
this.set_faces[value] = face;
}
Rate.prototype.setAdditionalData = function (data) {
this.settings.additional_data = data;
}
Rate.prototype.getAdditionalData = function () {
return this.settings.additional_data;
}
Rate.prototype.removeFace = function (value) {
delete this.set_faces[value];
}
Rate.prototype.setValue = function (value) {
if (!this.settings.readonly) {
if (value < 0) {
value = 0;
}
else if (value > this.settings.max_value) {
value = this.settings.max_value;
}
var old_value = this.getValue();
this.value = value;
/*
* About to change event, should support prevention later
*/
var change_event = $(this.element).trigger("change", {
"from": old_value,
"to": this.value
});
/*
* Set/Reset faces
*/
$(this.element).find(".rate-face").remove();
$(this.element).find("span").css({
visibility: 'visible'
});
var index_value = Math.ceil(this.value);
if (this.set_faces.hasOwnProperty(index_value)) {
var face = "<div>" + this.set_faces[index_value] + "</div>";
var base_layer_element = this.getElement('base-layer', index_value);
var select_layer_element = this.getElement('select-layer', index_value);
var hover_layer_element = this.getElement('hover-layer', index_value);
var left_pos = base_layer_element.textWidth() * (index_value - 1)
+ (base_layer_element.textWidth() - $(face).textWidth()) / 2;
$(face).appendTo(this.element).css({
display: 'inline-block',
position: 'absolute',
left: left_pos,
}).addClass("rate-face");
base_layer_element.css({
visibility: 'hidden'
});
select_layer_element.css({
visibility: 'hidden'
});
hover_layer_element.css({
visibility: 'hidden'
});
}
/*
* Set styles based on width and value
*/
if (!this.settings.only_select_one_symbol) {
var width = this.toWidth(this.value);
this.layers.select_layer.css({
display: 'block',
width: width + "%",
height: this.layers.base_layer.css("height")
});
this.layers.hover_layer.css({
display: 'none',
height: this.layers.base_layer.css("height")
});
}
else {
var width = this.toWidth(this.settings.max_value);
this.layers.select_layer.css({
display: 'block',
width: width + "%",
height: this.layers.base_layer.css("height")
});
this.layers.hover_layer.css({
display: 'none',
height: this.layers.base_layer.css("height")
});
this.layers.select_layer.children("span").css({
visibility: 'hidden',
});
this.layers.select_layer.children("span").eq(index_value != 0 ? index_value - 1 : 0).css({
visibility: 'visible',
});
}
// Update the data-rate-value attribute
$(this.element).attr("data-rate-value", this.value);
if (this.settings.change_once) {
this.settings.readonly = true;
}
this.updateServer();
/*
* After change event
*/
var change_event = $(this.element).trigger("afterChange", {
"from": old_value,
"to": this.value
});
/*
* Update custom input field if provided
*/
if (this.settings.hasOwnProperty("update_input_field_name")) {
this.settings.update_input_field_name.val(this.value);
}
}
}
Rate.prototype.increment = function () {
this.setValue(this.getValue() + this.settings.step_size);
}
Rate.prototype.decrement = function () {
this.setValue(this.getValue() - this.settings.step_size);
}
$.fn.rate.settings = {
max_value: 5,
step_size: 0.5,
initial_value: 0,
symbols: {
utf8_star: {
base: '\u2606',
hover: '\u2605',
selected: '\u2605',
},
utf8_hexagon: {
base: '\u2B21',
hover: '\u2B22',
selected: '\u2B22',
},
hearts: '♥',
fontawesome_beer: '<i class="fa fa-beer"></i>',
fontawesome_star: {
base: '<i class="fa fa-star-o"></i>',
hover: '<i class="fa fa-star"></i>',
selected: '<i class="fa fa-star"></i>',
},
utf8_emoticons: {
base: [0x1F625, 0x1F613, 0x1F612, 0x1F604],
hover: [0x1F625, 0x1F613, 0x1F612, 0x1F604],
selected: [0x1F625, 0x1F613, 0x1F612, 0x1F604],
},
},
selected_symbol_type: 'utf8_star', // Must be a key from symbols
convert_to_utf8: false,
cursor: 'default',
readonly: false,
change_once: false, // Determines if the rating can only be set once
only_select_one_symbol: false, // If set to true, only selects the hovered/selected symbol and nothing prior to it
ajax_method: 'POST',
additional_data: {}, // Additional data to send to the server
//update_input_field_name = some input field set by the user
};
}(jQuery, window));
//$(".rating").rate();
//or for example
var options = {
max_value: 5,
step_size: 1,
}
$(".rating").rate(options);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="rating" data-rate-value=6></div>
You are specifying the options at an earlier stage in your code.
See here:
$.fn.rate.settings = {
max_value: 5,
step_size: 0.5,
I tested in your fiddle and it works when updated to 1 step_size.

Adding a SetInterval function in jquery

So I have this code from here: j360
This code is perfect for what I want: an html wich has a draggable 360º product image view, but it lacks one thing: a button for auto rotation.
I already have the button into the html, but I can't, for more that I try, to make a function or anything to make the images go by itself, and not only when I drag it over the screen.
Here is the code I have in the moment.
(function($){
$.fn.j360 = function(options) {
var defaults = {
clicked: false,
currImg: 1
}
var options = jQuery.extend(defaults, options);
return this.each(function() {
var $obj = jQuery(this);
var aImages = {};
$obj.css({
'margin-left' : 'auto',
'margin-right' : 'auto',
'text-align' : 'center',
'overflow' : 'hide'
});
$overlay = $obj.clone(true);
$overlay.html('<img src="images/loader.gif" class="loader" style="margin-top:' + ($obj.height()/2 - 15) + 'px" />');
$overlay.attr('id', 'view_overlay');
$overlay.css({
'position' : 'absolute',
'z-index': '5',
'top' : $obj.offset().top,
'left' : $obj.offset().left,
'background' : '#fff'
});
$obj.after($overlay);
$obj.after('<div id="colors_ctrls"></div>');
jQuery('#colors_ctrls').css({
'width' : $obj.width(),
'position' : 'absolute',
'z-index': '5',
'top' : $obj.offset().top + $obj.height - 50,
'left' : $obj.offset().left
});
var imageTotal = 0;
jQuery('img', $obj).each(function() {
aImages[++imageTotal] = jQuery(this).attr('src');
preload(jQuery(this).attr('src'));
})
var imageCount = 0;
jQuery('.preload_img').load(function() {
if (++imageCount == imageTotal) {
$overlay.animate({
'filter' : 'alpha(Opacity=0)',
'opacity' : 0
}, 100);
$obj.html('<img src="' + aImages[1] + '" />');
$overlay.bind('mousedown touchstart', function(e) {
if (e.type == "touchstart") {
options.currPos = window.event.touches[0].pageX;
} else {
options.currPos = e.pageX;
}
options.clicked = true;
return false;
});
jQuery(document).bind('mouseup touchend', function() {
options.clicked = false;
});
jQuery(document).bind('mousemove touchmove', function(e) {
if (options.clicked) {
var pageX;
if (e.type == "touchmove") {
pageX = window.event.targetTouches[0].pageX;
} else {
pageX = e.pageX;
}
var width_step = 50;
if (Math.abs(options.currPos - pageX) >= width_step) {
if (options.currPos - pageX >= width_step) {
options.currImg++;
if (options.currImg > imageTotal) {
options.currImg = 1;
}
} else {
options.currImg--;
if (options.currImg < 1) {
options.currImg = imageTotal;
}
}
options.currPos = pageX;
$obj.html('<img src="' + aImages[options.currImg] + '" />');
}
}
});
}
});
if (jQuery.browser.msie || jQuery.browser.mozilla || jQuery.browser.opera || jQuery.browser.safari ) {
jQuery(window).resize(function() {
onresizeFunc($obj, $overlay);
});
} else {
var supportsOrientationChange = "onorientationchange" in window,
orientationEvent = supportsOrientationChange ? "orientationchange" : "resize";
window.addEventListener(orientationEvent, function() {
onresizeFunc($obj, $overlay);
}, false);
}
onresizeFunc($obj, $overlay)
});
}
})
(jQuery)
function onresizeFunc($obj, $overlay){
$obj.css({
'margin-top' : $(document).height()/2
});
$overlay.css({
'margin-top' : 200,
'top' : $obj.offset().top,
'left' : $obj.offset().left
});
jQuery('#colors_ctrls').css({
'top' : $obj.offset().top + $obj.height - 50,
'left' : $obj.offset().left
})
}
function preload(image) {
if (typeof document.body == "undefined") return;
try {
var div = document.createElement("div");
var s = div.style;
s.position = "absolute";
s.top = s.left = 0;
s.visibility = "hidden";
document.body.appendChild(div);
div.innerHTML = "<img class=\"preload_img\" src=\"" + image + "\" />";
}
catch(e) {
// Error. Do nothing.
}
};
I need a method to increment over time a function, to make the ilusion of auto-rotate.
This plugin doesn’t seem to have this option (a kind of autoplay) so you have to code it or search an other plugin.
Since it is a list of image, you can maybe don’t use the plugin and display images one after another with jQuery and .delay()

Fabricjs in textbox the font changes after newline

Have a little problem with the textbox. When setting font for text and type the text and pass enter for new line the font changes on Arial font. How can I save font after pass enter?
Cheers!
var text = new fabric.Textbox('Enter Text', {
width: imgInstance.width-20,
height: imgInstance.height,
fontSize: 16,
textAlign: 'left',
breakWords: true,
});
text.setTextAlign('center');
text.setColor('#D8C8A6')
text.setControlsVisibility({
mt: true,
mb: true,
ml: true,
mr: true,
bl: true,
br: true,
tl: true,
tr: true,
//mtr: false
});
text.set("top", imgInstance.top + 10);
text.set("left", imgInstance.left + 10);
if (array.includes("9") && $('.droplist-for-fixed-size').val() != "") {
var ourSize = $('#' + currentPlaqueId).val().split(" x ");
canvasHeight = $("#Height").val();
canvasWidth = $("#Width").val();
var newWidth = canvas.width * ourSize[0] / canvasWidth;
var newHeight = canvas.height * ourSize[1] / canvasHeight;
//var newHeight = oldWidth * ourSize[0] / ourSize[1];
imgInstance.set("height", newHeight);
imgInstance.set("width", newWidth);
text.set("height", newHeight);
text.set("width", newWidth-20);
}
var group = new fabric.Group([imgInstance, text]);
window.checkProp(array, group);
window.enterEditForGroup(group)
Don't see the reason to insert code, because has a problem with the standard function, but if u want.
Little example of image
Part of code where i change the font:
function setStyle(object, styleName, value) {
if (object._objects != null) {
object = object._objects[1];
}
if (object.setSelectionStyles && object.isEditing) {
var style = { };
style[styleName] = value;
object.setSelectionStyles(style);
}
else {
object[styleName] = value;
}
}
function getStyle(object, styleName) {
return (object.getSelectionStyles && object.isEditing)
? object.getSelectionStyles()[styleName]
: object[styleName];
}
function addHandler(id, fn, eventName) {
if (document.getElementById(id)){
document.getElementById(id)[eventName || 'onclick'] = function() {
var el = this;
//canvases.forEach(function(canvas, obj) {
if (obj = canvas.getActiveObject()) {
fn.call(el, obj);
canvas.renderAll();
}
}
//});
};
}
addHandler('font-family', function (obj) {
setStyle(obj, 'fontFamily', this.value);
}, 'onchange');

Fullpage jquery navigation

Good day guys. I got a small question here.
kindly check this link
http://www.thepetedesign.com/demos/fullpagenav_demo.html
How can I make the transition of hovering out be more smoother more like this
http://outdatedbrowser.com/
I'm a designer and know just a tiny bit about jquery but I think the smoothness can be inserted here?
.mouseleave(function() {
if (!li.find(".fpn_wrap").hasClass("fpn_clicked")) {
$(this).removeClass("active")
el.recalculate(settings, width);
el.find(".fpn_wrap").finish().css({width: "100%"})
}
});
here's the full code
!function($){
var defaults = {
columns: 5,
selector: "> li",
hover_size: "30%",
animateDuration: 500,
animateFrom: "left",
clickable: true,
afterClicked: null
};
$.fn.recalculate = function(settings, width) {
var el = $(this),
active = false,
total = el.find(settings.selector).length,
last_pos = 0,
total_width = 0;
if(el.find(".fpn_li.active").length > 0) {
el.find(".fpn_li.active").css({
width: settings.hover_size
});
var small_width = (100 - parseFloat(settings.hover_size))/(settings.columns - 1)
el.find(".fpn_li:not(.active)").css({
width: small_width + "%"
});
el.find(settings.selector).each(function( index, value ) {
if ($(this).prev(".fpn_li").length > 0) {
if($(this).prev(".fpn_li").hasClass("active")) {
var w = settings.hover_size
} else {
var w = small_width
}
var left = total_width + parseFloat(w)
$(this).finish().animate({
left: left + "%"
}, settings.animateDuration, function() {
$(this).css({
left: left + "%"
});
})
total_width = total_width + parseFloat(w)
}
});
} else {
el.find(settings.selector).each(function( index, value ) {
$(this).finish().animate({
width: width + "%",
left: (width * index) + "%"
}, settings.animateDuration);
});
}
}
function determineDirection($el, pos){
var w = $el.width(),
middle = $el.offset().left + w/2;
return (pos.pageX > middle ? 0 : 1);
}
$.fn.fullpagenav = function(options){
var settings = $.extend({}, defaults, options),
el = $(this),
width = 100 / settings.columns;
el.addClass("fullpagenav").find(settings.selector).addClass("fpn_li")
el.parent().addClass("fpn_body")
el.find(settings.selector).each(function( index, value ) {
var li = $(this);
li.css({
width: width + "%",
left: (width * index) + "%"
});
li.wrapInner("<div class='fpn_wrap'></div>")
if (settings.clickable == true && li.data("link")) {
li.css({cursor: "pointer"}).click(function(e) {
if (!li.find(".fpn_wrap").hasClass("fpn_clicked")) {
li.find(".fpn_wrap > img").css({
margin: 0,
padding: 0,
left: 0,
maxHeight: "inherit"
}).animate({
width: "100%"
})
li.find(".fpn_wrap").addClass("fpn_clicked").css({position: "fixed", "z-index": 99}).finish().animate({
width: "100%", top: 0, left: 0
}, settings.animationDuration, function() {
e.preventDefault()
if (typeof settings.afterClicked == 'function') return settings.afterClicked(li.data("link"));
window.location.href = li.data("link");
});
} else {
li.find(".fpn_wrap").removeClass("fpn_clicked").finish().animate({
width: "0%", top: 0, left: 0, height: "0%"
}, settings.animationDuration, function() {
$(this).attr("style","").find("> img").attr("style","")
})
}
});
}
li.mouseenter(function(e) {
if (!li.find(".fpn_wrap").hasClass("fpn_clicked")) {
$(this).finish().addClass("active")
el.recalculate(settings, width);
if (settings.animateFrom == "auto") {
if(determineDirection(li, e) == 1) {
$(this).find(".fpn_wrap").finish().css({ float: "left"}).animate({width: el.find(".fpn_li.active").width()}, settings.animateDuration)
} else {
$(this).find(".fpn_wrap").finish().css({ float: "right"}).animate({width: el.find(".fpn_li.active").width()}, settings.animateDuration)
}
} else {
$(this).find(".fpn_wrap").finish().css({ float: settings.animateFrom}).animate({width: el.find(".fpn_li.active").width()}, settings.animateDuration)
}
}
}).mouseleave(function() {
if (!li.find(".fpn_wrap").hasClass("fpn_clicked")) {
$(this).removeClass("active")
el.recalculate(settings, width);
el.find(".fpn_wrap").finish().css({width: "100%"})
}
});
});
}
}(window.jQuery);
Thanks : )

e.stopPropagation on children during webkit-transform rotate?

I have a circle that has a menu with objects positioned in a circle inside the circle. When the user drags their finger in any direction the circle spins along with the finger, however, when the user is spinning the wheel and happens to touch one of the objects inside (serves as a link) the circle "brakes" and stops spinning. I need to cancel the event bubble. I know I need to use event.stopPropagation and deal with the capture inside the event handler but I am having issues implementing this with a jQuery plugin (Touchy) that I found.
*MAKE SURE YOU ARE VIEWING IN CHROME WITH THE OVERRIDES SET TO EMULATE TOUCH EVENTS AND THAT THE DEVELOPER CONSOLE IS OPEN Command+Option+i on Mac!!! *
http://jsfiddle.net/ymtV3/
*MAKE SURE YOU ARE VIEWING IN CHROME WITH THE OVERRIDES SET TO EMULATE TOUCH EVENTS AND THAT THE DEVELOPER CONSOLE IS OPEN Command+Option+i on Mac!!! *
<div id="wheelMenu">
<div id="wheel">
<ul class="items">
<li class="flashOff"><span>Flash</span></li>
<li class="sceneOff"><span>Scene</span></li>
<li class="hdrOff"><span>Hdr</span></li>
<li class="panoramaOff"><span>Pana</span></li>
<li class="resolutionOff"><span>Resolu</span></li>
<li class="reviewOff"><span>Review</span></li>
<li class="continuousShootingOff"><span>Contin</span></li>
<li class="dropBoxOff"><span>DropBox</span></li>
<li class="timerOff"><span>Timer</span></li>
</ul>
</div>
</div>
(function ($) {
$.touchyOptions = {
useDelegation: false,
rotate: {
preventDefault: {
start: true,
move: true,
end: true
},
stopPropagation: {
start: true,
move: true,
end: true
},
requiredTouches: 1,
data: {},
proxyEvents: ["TouchStart", "TouchMove", "GestureChange", "TouchEnd"]
}
};
var proxyHandlers = {
handleTouchStart: function (e) {
var eventType = this.context,
$target = getTarget(e, eventType);
if ($target) {
var event = e.originalEvent,
touches = event.targetTouches,
camelDataName = 'touchy' + eventType.charAt(0).toUpperCase() + eventType.slice(1),
data = $target.data(camelDataName),
settings = data.settings;
if (settings.preventDefault.start) {
event.preventDefault();
}
if (settings.stopPropagation.start) {
event.stopPropagation();
}
if (touches.length === settings.requiredTouches) {
switch (eventType) {
case 'rotate':
if (touches.length === 1) {
ensureSingularStartData(data, touches, e.timeStamp);
console.log(eventType);
console.log(touches);
console.log(data);
} else {
var points = getTwoTouchPointData(e);
data.startPoint = {
"x": points.centerX,
"y": points.centerY
};
data.startDate = e.timeStamp;
}
var startPoint = data.startPoint;
$target.trigger('touchy-rotate', ['start', $target, {
"startPoint": startPoint,
"movePoint": startPoint,
"lastMovePoint": startPoint,
"velocity": 0,
"degrees": 0
}]);
break;
}
}
}
},
handleTouchMove: function (e) {
var eventType = this.context,
$target = getTarget(e, eventType);
if ($target) {
var event = e.originalEvent,
touches = event.targetTouches,
camelDataName = 'touchy' + eventType.charAt(0).toUpperCase() + eventType.slice(1),
data = $target.data(camelDataName),
settings = data.settings;
if (settings.preventDefault.move) {
event.preventDefault();
}
if (settings.stopPropagation.move) {
event.stopPropagation();
}
if (touches.length === settings.requiredTouches) {
switch (eventType) {
case 'rotate':
var lastMovePoint,
lastMoveDate,
movePoint,
moveDate,
lastMoveDate,
distance,
ms,
velocity,
targetPageCoords,
centerCoords,
radians,
degrees,
lastDegrees,
degreeDelta;
lastMovePoint = data.lastMovePoint = data.movePoint || data.startPoint;
lastMoveDate = data.lastMoveDate = data.moveDate || data.startDate;
movePoint = data.movePoint = {
"x": touches[0].pageX,
"y": touches[0].pageY
};
moveDate = data.moveDate = e.timeStamp;
if (touches.length === 1) {
targetPageCoords = data.targetPageCoords = data.targetPageCoords || getViewOffset(e.target);
centerCoords = data.centerCoords = data.centerCoords || {
"x": targetPageCoords.x + ($target.width() * 0.5),
"y": targetPageCoords.y + ($target.height() * 0.5)
};
} else {
var points = getTwoTouchPointData(e);
centerCoords = data.centerCoords = {
"x": points.centerX,
"y": points.centerY
};
if (hasGestureChange()) {
break;
}
}
radians = Math.atan2(movePoint.y - centerCoords.y, movePoint.x - centerCoords.x);
lastDegrees = data.lastDegrees = data.degrees;
degrees = data.degrees = radians * (180 / Math.PI);
degreeDelta = lastDegrees ? degrees - lastDegrees : 0;
ms = moveDate - lastMoveDate;
velocity = data.velocity = ms === 0 ? 0 : degreeDelta / ms;
$target.trigger('touchy-rotate', ['move', $target, {
"startPoint": data.startPoint,
"startDate": data.startDate,
"movePoint": movePoint,
"lastMovePoint": lastMovePoint,
"centerCoords": centerCoords,
"degrees": degrees,
"degreeDelta": degreeDelta,
"velocity": velocity
}]);
break;
}
}
}
},
handleGestureChange: function (e) {
var eventType = this.context,
$target = getTarget(e, eventType);
if ($target) {
var $target = $(e.target),
event = e.originalEvent,
touches = event.touches,
camelDataName = 'touchy' + eventType.charAt(0).toUpperCase() + eventType.slice(1),
data = $target.data(camelDataName);
if (data.preventDefault.move) {
event.preventDefault();
}
if (settings.stopPropagation.move) {
event.stopPropagation();
}
switch (eventType) {
case 'rotate':
var lastDegrees = data.lastDegrees = data.degrees,
degrees = data.degrees = event.rotation,
degreeDelta = lastDegrees ? degrees - lastDegrees : 0,
ms = data.moveDate - data.lastMoveDate,
velocity = data.velocity = ms === 0 ? 0 : degreeDelta / ms;
$target.trigger('touchy-rotate', ['move', $target, {
"startPoint": data.startPoint,
"startDate": data.startDate,
"movePoint": data.movePoint,
"lastMovePoint": data.lastMovePoint,
"centerCoords": data.centerCoords,
"degrees": degrees,
"degreeDelta": degreeDelta,
"velocity": velocity
}]);
break;
}
}
},
handleTouchEnd: function (e) {
var eventType = this.context,
$target = getTarget(e, eventType);
if ($target) {
var event = e.originalEvent,
camelDataName = 'touchy' + eventType.charAt(0).toUpperCase() + eventType.slice(1),
data = $target.data(camelDataName),
settings = data.settings;
if (settings.preventDefault.end) {
event.preventDefault();
}
if (settings.stopPropagation.end) {
event.stopPropagation();
}
switch (eventType) {
case 'rotate':
var degreeDelta = data.lastDegrees ? data.degrees - data.lastDegrees : 0;
$target.trigger('touchy-rotate', ['end', $target, {
"startPoint": data.startPoint,
"startDate": data.startDate,
"movePoint": data.movePoint,
"lastMovePoint": data.lastMovePoint,
"degrees": data.degrees,
"degreeDelta": degreeDelta,
"velocity": data.velocity
}]);
$.extend(data, {
"startPoint": null,
"startDate": null,
"movePoint": null,
"moveDate": null,
"lastMovePoint": null,
"lastMoveDate": null,
"targetPageCoords": null,
"centerCoords": null,
"degrees": null,
"lastDegrees": null,
"velocity": null
});
break;
}
}
}
},
ensureSingularStartData = function (data, touches, timeStamp) {
if (!data.startPoint) {
data.startPoint = {
"x": touches[0].pageX,
"y": touches[0].pageY
}
}
if (!data.startDate) {
data.startDate = timeStamp;
}
},
hasGestureChange = function () {
return (typeof window.ongesturechange == "object");
},
getTarget = function (e, eventType) {
var $delegate,
$target = false,
i = 0,
len = boundElems[eventType].length
if ($.touchyOptions.useDelegation) {
for (; i < len; i += 1) {
$delegate = $(boundElems[eventType][i]).has(e.target);
if ($delegate.length > 0) {
$target = $delegate;
break;
}
}
} else if (boundElems[eventType] && boundElems[eventType].index(e.target) != -1) {
$target = $(e.target)
}
return $target;
},
getViewOffset = function (node, singleFrame) {
function addOffset(node, coords, view) {
var p = node.offsetParent;
coords.x += node.offsetLeft - (p ? p.scrollLeft : 0);
coords.y += node.offsetTop - (p ? p.scrollTop : 0);
if (p) {
if (p.nodeType == 1) {
var parentStyle = view.getComputedStyle(p, '');
if (parentStyle.position != 'static') {
coords.x += parseInt(parentStyle.borderLeftWidth);
coords.y += parseInt(parentStyle.borderTopWidth);
if (p.localName == 'TABLE') {
coords.x += parseInt(parentStyle.paddingLeft);
coords.y += parseInt(parentStyle.paddingTop);
} else if (p.localName == 'BODY') {
var style = view.getComputedStyle(node, '');
coords.x += parseInt(style.marginLeft);
coords.y += parseInt(style.marginTop);
}
} else if (p.localName == 'BODY') {
coords.x += parseInt(parentStyle.borderLeftWidth);
coords.y += parseInt(parentStyle.borderTopWidth);
}
var parent = node.parentNode;
while (p != parent) {
coords.x -= parent.scrollLeft;
coords.y -= parent.scrollTop;
parent = parent.parentNode;
}
addOffset(p, coords, view);
}
} else {
if (node.localName == 'BODY') {
var style = view.getComputedStyle(node, '');
coords.x += parseInt(style.borderLeftWidth);
coords.y += parseInt(style.borderTopWidth);
var htmlStyle = view.getComputedStyle(node.parentNode, '');
coords.x -= parseInt(htmlStyle.paddingLeft);
coords.y -= parseInt(htmlStyle.paddingTop);
}
if (node.scrollLeft) coords.x += node.scrollLeft;
if (node.scrollTop) coords.y += node.scrollTop;
var win = node.ownerDocument.defaultView;
if (win && (!singleFrame && win.frameElement)) addOffset(win.frameElement, coords, win);
}
}
......seee the rest on the JS Fiddle

Categories

Resources