]> source.dussan.org Git - gwtquery.git/commitdiff
Adding Transitions plugin for CSS3 animations
authorManuel Carrasco Moñino <manuel.carrasco.m@gmail.com>
Fri, 1 Nov 2013 19:05:57 +0000 (20:05 +0100)
committerManuel Carrasco Moñino <manuel.carrasco.m@gmail.com>
Fri, 1 Nov 2013 19:05:57 +0000 (20:05 +0100)
gwtquery-core/src/main/java/com/google/gwt/query/client/GQuery.java
gwtquery-core/src/main/java/com/google/gwt/query/client/plugins/effects/Transitions.java [new file with mode: 0644]

index 1ef33fbc803cd9baf47afb2b1a2f348c7faee19f..758fcac906abc1d171c283d94964ee903b9cedde 100644 (file)
@@ -49,6 +49,8 @@ import com.google.gwt.query.client.plugins.ajax.Ajax;
 import com.google.gwt.query.client.plugins.ajax.Ajax.Settings;
 import com.google.gwt.query.client.plugins.deferred.Deferred;
 import com.google.gwt.query.client.plugins.effects.PropertiesAnimation.Easing;
+import com.google.gwt.query.client.plugins.effects.PropertiesAnimation.EasingCurve;
+import com.google.gwt.query.client.plugins.effects.Transitions;
 import com.google.gwt.query.client.plugins.events.EventsListener;
 import com.google.gwt.query.client.plugins.widgets.WidgetsUtils;
 import com.google.gwt.regexp.shared.RegExp;
@@ -134,6 +136,11 @@ public class GQuery implements Lazy<GQuery, LazyGQuery> {
    * Static reference Effects plugin
    */
   public static Class<Effects> Effects = com.google.gwt.query.client.plugins.Effects.Effects;
+  
+  /**
+   * Static reference Transitions plugin
+   */
+  public static Class<Transitions> Transitions = com.google.gwt.query.client.plugins.effects.Transitions.Transitions;
 
   /**
    * Implementation engine used for CSS selectors.
@@ -699,6 +706,13 @@ public class GQuery implements Lazy<GQuery, LazyGQuery> {
   public static LazyGQuery<?> lazy() {
     return $().createLazy();
   }
+  
+  /**
+   * Dump an object to the window.console.log when available
+   */
+  public static void log(Object o) {
+    JsUtils.log(o);
+  }
 
   /**
    * Perform an ajax request to the server using POST.
@@ -4350,6 +4364,37 @@ public class GQuery implements Lazy<GQuery, LazyGQuery> {
   public String toString() {
     return toString(false);
   }
+  
+  /**
+   * The transition() method allows you to create animation effects on any numeric HTML Attribute,
+   * CSS property, or color using CSS3 transformations and transitions.
+   * 
+   * It works similar to animate()
+   *
+   * Example:
+   * $("#foo").transition("{ opacity: 0.1, scale: 2, x: 50, y: 50 }", 5000, EasingCurve.easeInBack);
+   *
+   */
+  public GQuery transition(Object stringOrProperties, int duration, Easing easing, Function... funcs) {
+    return transition(stringOrProperties, duration, 0, easing, funcs);
+  }
+  
+  /**
+   * The transition() method allows you to create animation effects on any numeric HTML Attribute,
+   * CSS property, or color using CSS3 transformations and transitions.
+   * 
+   * It works similar to animate() but has an extra parameter for delaying the animation, so as
+   * we dont have to use GQuery queue system for delaying executions, nor callbacks for firing more
+   * animations
+   *
+   * Example animate an element within 2 seconds:
+   * $("#foo")
+   *   .transition("{ opacity: 0.1, scale: 2, x: 50, y: 50 }", 5000, 2000, EasingCurve.easeInBack);
+   *
+   */
+  public GQuery transition(Object stringOrProperties, int duration, int delay, Easing easing, Function... funcs) {
+    return as(Transitions).transition(stringOrProperties, duration, easing, delay, funcs);
+  }
 
   /**
    * Produces a string representation of the matched elements.
diff --git a/gwtquery-core/src/main/java/com/google/gwt/query/client/plugins/effects/Transitions.java b/gwtquery-core/src/main/java/com/google/gwt/query/client/plugins/effects/Transitions.java
new file mode 100644 (file)
index 0000000..e31742d
--- /dev/null
@@ -0,0 +1,298 @@
+/*
+ * Copyright 2013, The gwtquery team.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.query.client.plugins.effects;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map.Entry;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.query.client.Function;
+import com.google.gwt.query.client.GQuery;
+import com.google.gwt.query.client.Properties;
+import com.google.gwt.query.client.js.JsUtils;
+import com.google.gwt.query.client.plugins.Plugin;
+import com.google.gwt.query.client.plugins.effects.PropertiesAnimation.Easing;
+import com.google.gwt.query.client.plugins.effects.PropertiesAnimation.EasingCurve;
+import com.google.gwt.regexp.shared.MatchResult;
+import com.google.gwt.regexp.shared.RegExp;
+import com.google.gwt.user.client.DOM;
+
+/**
+ * Transitions and transformation plugin for gQuery.
+ * 
+ * It is inspired on jquery.transit (http://github.com/rstacruz/jquery.transit)
+ *
+ * Usage examples:
+ * <pre> 
+
+    $("#foo")
+     .transition("{ opacity: 0.1, scale: 2, x: 50, y: 50 }", 5000, "easeOutBack", 0)
+     .promise().done(new Function(){public void f() {
+        g1.transition("{x: +100}", 2000, "linear", 0);
+     }});
+
+ * </pre>
+ */
+public class Transitions extends GQuery {
+  
+  /**
+   * A dictionary class with all the properties of an element transform
+   * which is able to return the correct syntax for setting css properties.
+   */
+  public static class Transform  {
+    
+    private static final RegExp transform3dRegex = RegExp.compile("^(rotate([XY]|3d)|perspective)$");
+    
+    private HashMap<String, List<String>> map = new HashMap<String, List<String>>();
+
+    public Transform(String s) {
+      parse(s);
+    }
+    
+    public List<String> get(String prop) {
+      return map.get(prop);
+    }
+    
+    private void parse(String s) {
+      if (s != null) {
+        RegExp re = RegExp.compile("([a-zA-Z0-9]+)\\((.*?)\\)", "g");
+        for (MatchResult r = re.exec(s); r != null; r = re.exec(s)) {
+          setFromString(r.getGroup(1), r.getGroup(2));
+        }
+      }
+    }
+    
+    public void set(String prop, String ...val) {
+      setter(prop, val);
+    }
+    
+    public void setFromString(String prop, String ...val) {
+      if (val.length == 0 && val[0] instanceof String) {
+        String[] vals = ((String)val[0]).split("[\\s*,\\s*]");
+        set(prop, vals);
+      } else {
+        set(prop, val);
+      }
+    }
+    
+    private void setter(String prop, String ...val) {
+      if (prop.matches("(rotate[XY]?|skew[XY])")) {
+        map.put(prop, unit(val[0], "deg"));
+      } else if ("scale".equals(prop)) {
+        String x = val.length < 1 ? "1" : val[0];
+        String y = val.length < 2 ? x : val[1];
+        map.put(prop, Arrays.asList(x, y));
+      } else if ("perspective".equals(prop)) {
+        map.put(prop, unit(val[0], "px"));
+      } else if ("x".equals(prop)) {
+        setter("translate", val[0], null);
+      } else if ("y".equals(prop)) {
+        setter("translate", null, val[0]);
+      } else if ("translate".equals(prop)) {
+        if (map.get("translateX") == null) {
+          map.put("translateX", unit("0", "px"));
+        }
+        if (val[0] != null) {
+          map.put("translateX", unit(val[0], "px"));
+        }
+        if (map.get("translateY") == null) {
+          map.put("translateY", unit("0", "px"));
+        }
+        if (val[1] != null) {
+          map.put("translateY", unit(val[1], "px"));
+        }
+        map.put("translate", Arrays.asList(map.get("translateX").get(0), map.get("translateY").get(0)));
+      }
+    }
+    
+    /**
+     * Converts the dictionary to a transition css string.
+     */
+    public String toString() {
+      // purposely using string addition, since my last tests demonstrate
+      // that string addition performs better than string builders in gwt-prod.
+      String ret = "";
+      for (Entry<String, List<String>> e: map.entrySet()) {
+        if (has3d || !transform3dRegex.test(e.getKey())) {
+          String v = "";
+          for (String s : e.getValue()) {
+            v += (v.isEmpty() ? "" : ",") + s;
+          }
+          ret += (ret.isEmpty() ? "" : " ") + e.getKey() + "(" + v + ")";
+        }
+      }
+      return ret;
+    }
+    
+    private List<String> unit(String val, String unit) {
+      return Arrays.asList(val + (val.endsWith(unit) ? "" : unit));
+    }
+  }
+  
+  // Used to check supported properties in the browser
+  private static Style divStyle = DOM.createDiv().getStyle();
+  
+  private static final String prefix = browser.msie ? "ms" : browser.opera ? "O" : browser.mozilla ? "Moz" : browser.webkit ? "Webkit" : "";
+  private static final String transform = getVendorPropertyName("transform");
+  private static final String TRANSFORM = "_t_";
+  private static final String transformOrigin = getVendorPropertyName("transformOrigin");
+  
+  private static final RegExp transformRegex = RegExp.compile("^(scale|translate|rotate([XY]|3d)?|perspective|skew[XY]|x|y)$");
+  private static final String transition = getVendorPropertyName("transition");
+  
+  private static final String transitionDelay = getVendorPropertyName("transitionDelay");
+  private static final String transitionEnd = browser.mozilla ? "transitionEnd" : (prefix + "transitionEnd");
+  
+  public static boolean has3d = supportsTransform3d();
+  
+  public static final Class<Transitions> Transitions = GQuery.registerPlugin(
+      Transitions.class, new Plugin<Transitions>() {
+        public Transitions init(GQuery gq) {
+          return new Transitions(gq);
+        }
+      });
+  
+  private static String getVendorPropertyName(String prop) {
+    if (JsUtils.hasProperty(divStyle, prop)) return prop;
+    String camelProp = JsUtils.camelize(prop);
+    if (JsUtils.hasProperty(divStyle, camelProp)) return camelProp;
+    String vendorProp =  prefix + camelProp;
+    if (JsUtils.hasProperty(divStyle, vendorProp)) return vendorProp;
+    return null;
+  }
+  
+  private static String property(String prop) {
+    if (transformRegex.test(prop)) {
+      return "transform";
+    }
+    return prop.replaceFirst("^(margin|padding).+$", "$1");
+  }
+  
+  private static boolean supportsTransform3d() {
+    String rotate = "rotateY(1deg)";
+    divStyle.setProperty(transform, rotate);
+    rotate = divStyle.getProperty(transform);
+    return rotate != null && !rotate.isEmpty();
+  }
+  
+  protected Transitions(GQuery gq) {
+    super(gq);
+  }
+
+  @Override 
+  public String css(String prop, boolean force) {
+    if ("transform".equals(prop)) {
+      Transform t = data(TRANSFORM);
+      return t == null ? "" : t.toString();
+    } else if ("transformOrigin".equals(prop)) {
+      return super.css(transformOrigin, force);
+    } else if ("transition".equals(prop)) {
+      return super.css(transition, force);
+    } else {
+      return super.css(prop, force);
+    }
+  }
+  
+  @Override 
+  public Transitions css(String prop, String value) {
+    if ("transform".equals(prop)) {
+      for (Element e : elements()) {
+        Transform t = getTransform(e, value);
+        getStyleImpl().setStyleProperty(e, transform, t.toString());
+      }
+    } else if ("transformOrigin".equals(prop)) {
+      super.css(transformOrigin, value);
+    } else if ("transition".equals(prop)) {
+      super.css(transition, value);
+    } else if (transformRegex.test(prop)) {
+      for (Element e : elements()) {
+        Transform t = getTransform(e, null);
+        t.setFromString(prop, value);
+        getStyleImpl().setStyleProperty(e, transform, t.toString());
+      }
+    } else {
+      super.css(prop, value);
+    }
+    return this;
+  }
+  
+  private List<String> filterPropertyNames(Properties p) {
+    List<String> ret = new ArrayList<String>();
+    for (String s : p.keys()) {
+      String c = JsUtils.camelize(s);
+      // marginLeft, marginRight ...  -> margin
+      String m = property(c);
+      if (m != null) {
+        c = m;
+      }
+      if (!ret.contains(c)) {
+        ret.add(c);
+      }
+    }
+    return ret;
+  }
+  
+  private Transform getTransform(Element e, String initial) {
+    Transform t = data(e, TRANSFORM);
+    if (t == null || initial != null && !initial.isEmpty() ) {
+      t = new Transform(initial);
+      data(e, TRANSFORM, t);
+    }
+    return t;
+  }
+  
+  /**
+   * Works like GQuery.animate(), but uses CSS transitions.
+   */
+  public Transitions transition(Object stringOrProperties, int duration, Easing easing, int delay, final Function... funcs) {
+    final Properties p = (stringOrProperties instanceof String) ? $$((String) stringOrProperties) : (Properties) stringOrProperties;
+
+    final String oldTransitions = css(transition);
+
+    if (easing == null) {
+      easing = EasingCurve.ease;
+    }
+
+    String attribs = duration + "ms" + " "  + easing.toString() + " " + delay + "ms";
+    
+    List<String> props = filterPropertyNames(p);
+    String value  = "";
+    for (String s : props) {
+      value += (value.isEmpty() ? "" : ", ") + s + " " + attribs;
+    }    
+    css(transition, value);
+
+    // schedule setting css animated properties so as we are sure css transition property has been set.
+    delay(0, new Function(){public void f() {
+      css(p);
+    }});
+    
+    // restore oldTransitions in the element, and use the queue to prevent more effects being run.
+    // TODO: Use transitionEnd events once GQuery supports non-bit events
+    delay(duration, new Function(){public void f() {
+      css(transition, oldTransitions);
+      each(funcs);
+    }});
+    
+    return this;
+  }
+
+  public Transitions transition(Object stringOrProperties, int duration, String easing, int delay) {
+    return transition(stringOrProperties, duration, EasingCurve.valueOf(easing), delay);
+  }
+}