You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Transform.java 7.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. /*
  2. * Copyright 2014, The gwtquery team.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.google.gwt.query.client.plugins.effects;
  17. import static com.google.gwt.query.client.plugins.Effects.prefix;
  18. import static com.google.gwt.query.client.plugins.Effects.vendorPropNames;
  19. import static com.google.gwt.query.client.plugins.Effects.vendorProperty;
  20. import com.google.gwt.dom.client.Document;
  21. import com.google.gwt.dom.client.Element;
  22. import com.google.gwt.dom.client.Style;
  23. import com.google.gwt.query.client.GQuery;
  24. import com.google.gwt.query.client.js.JsUtils;
  25. import com.google.gwt.regexp.shared.MatchResult;
  26. import com.google.gwt.regexp.shared.RegExp;
  27. import java.util.Arrays;
  28. import java.util.HashMap;
  29. import java.util.List;
  30. import java.util.Map.Entry;
  31. /**
  32. * A dictionary class with all the properties of an element transform
  33. * which is able to return the correct syntax for setting the CSS transform
  34. * property.
  35. */
  36. public class Transform {
  37. private static final String TRANSFORM = "_t_";
  38. // Used to check supported properties in the browser
  39. protected static final Style divStyle = Document.get().createDivElement().getStyle();
  40. // Compute browser specific constants, public so as they are usable in plugins
  41. static {
  42. for (String s: new String[]{"transition", "transitionDelay", "transform", "transformOrigin"}) {
  43. vendorPropNames.put(s, getVendorPropertyName(s));
  44. }
  45. // x,y,z are aliases
  46. for (String s: new String[]{"x", "y", "z"}) {
  47. vendorPropNames.put(s, "translate" + s.toUpperCase());
  48. }
  49. }
  50. public static final String TRANSFORM_1 = vendorProperty("transform");
  51. public static final String TRANSFORM_ORIGIN = vendorProperty("transformOrigin");
  52. // Non final for testing purposes.
  53. public static boolean has3d = supportsTransform3d();
  54. // Regular expressions based on http://www.w3schools.com/cssref/css3_pr_transform.asp
  55. protected static final RegExp transformRegex = RegExp.compile("^(matrix(3d)?|(translate|scale|rotate)([XYZ]|3d)?|skew([XY])?|perspective|x|y|z)$");
  56. private static final RegExp transform3dRegex = RegExp.compile("^(rotate[XY]|\\w+(Z|3d)|perspective)$");
  57. private static final RegExp transformParseRegex = RegExp.compile("(\\w+)\\((.*?)\\)", "g");
  58. private static final RegExp anglePropRegex = RegExp.compile("(rotate[XYZ]?|skew[XY]?)");
  59. private static final RegExp translatePropRegex = RegExp.compile("translate[XYZ]");
  60. private HashMap<String, List<String>> map = new HashMap<>();
  61. // Some browsers like HTMLUnit only support 2d transformations
  62. private static boolean supportsTransform3d() {
  63. if (TRANSFORM_1 == null) {
  64. return false;
  65. }
  66. String rotate = "rotateY(1deg)";
  67. divStyle.setProperty(TRANSFORM_1, rotate);
  68. rotate = divStyle.getProperty(TRANSFORM_1);
  69. return rotate != null && !rotate.isEmpty();
  70. }
  71. /**
  72. * Compute the correct CSS property name for a specific browser vendor.
  73. */
  74. public static String getVendorPropertyName(String prop) {
  75. // we prefer vendor specific names by default
  76. String vendorProp = JsUtils.camelize("-" + prefix + "-" + prop);
  77. if (JsUtils.hasProperty(divStyle, vendorProp)) {
  78. return vendorProp;
  79. }
  80. if (JsUtils.hasProperty(divStyle, prop)) {
  81. return prop;
  82. }
  83. String camelProp = JsUtils.camelize(prop);
  84. if (JsUtils.hasProperty(divStyle, camelProp)) {
  85. return camelProp;
  86. }
  87. return null;
  88. }
  89. /**
  90. * Return the Transform dictionary object of a element.
  91. */
  92. public static Transform getInstance(Element e) {
  93. return getInstance(e, null);
  94. }
  95. /**
  96. * Return true if the propName is a valid value of the css3 transform property.
  97. */
  98. public static boolean isTransform(String propName) {
  99. return transformRegex.test(propName);
  100. }
  101. /**
  102. * Return the Transform dictionary object of an element, but reseting
  103. * historical values and setting them to the initial value passed.
  104. */
  105. public static Transform getInstance(Element e, String initial) {
  106. Transform t = GQuery.data(e, TRANSFORM);
  107. if (t == null || initial != null) {
  108. if (initial == null) {
  109. initial = GQuery.getSelectorEngine().getDocumentStyleImpl().curCSS(e, TRANSFORM_1, false);
  110. }
  111. t = new Transform(initial);
  112. GQuery.data(e, TRANSFORM, t);
  113. }
  114. return t;
  115. }
  116. /**
  117. * Create a new Transform dictionary setting initial values based on the
  118. * string passed.
  119. */
  120. public Transform(String s) {
  121. parse(s);
  122. }
  123. /**
  124. * Return the value of a transform property.
  125. */
  126. public String get(String prop) {
  127. return listToStr(map.get(prop), ",");
  128. }
  129. private String listToStr(List<String> l, String sep) {
  130. String v = "";
  131. if (l != null) {
  132. for (String s : l) {
  133. v += (v.isEmpty() ? "" : sep) + s;
  134. }
  135. }
  136. return v;
  137. }
  138. /**
  139. * Parse a transform value as string and fills the dictionary map.
  140. */
  141. private void parse(String s) {
  142. if (s != null) {
  143. for (MatchResult r = transformParseRegex.exec(s); r != null; r = transformParseRegex.exec(s)) {
  144. setFromString(vendorProperty(r.getGroup(1)), r.getGroup(2));
  145. }
  146. }
  147. }
  148. /**
  149. * Set a transform value or multi-value.
  150. */
  151. public void set(String prop, String ...val) {
  152. setter(prop, val);
  153. }
  154. /**
  155. * Set a transform multi-value giving either a set of strings or
  156. * just an string of values separated by comma.
  157. */
  158. public void setFromString(String prop, String ...val) {
  159. if (val.length == 1) {
  160. String[] vals = val[0].split("[\\s,]+");
  161. set(prop, vals);
  162. } else {
  163. set(prop, val);
  164. }
  165. }
  166. private void setter(String prop, String ...val) {
  167. if (anglePropRegex.test(prop)) {
  168. map.put(prop, unit(val[0], "deg"));
  169. } else if ("scale".equals(prop)) {
  170. String x = val.length < 1 ? "1" : val[0];
  171. String y = val.length < 2 ? x : val[1];
  172. map.put(prop, Arrays.asList(x, y));
  173. } else if ("perspective".equals(prop)) {
  174. map.put(prop, unit(val[0], "px"));
  175. } else if (translatePropRegex.test(prop)) {
  176. map.put(prop, unit(val[0], "px"));
  177. } else if ("translate".equals(prop)) {
  178. if (val[0] != null) {
  179. map.put("translateX", unit(val[0], "px"));
  180. }
  181. if (val.length > 1 && val[1] != null) {
  182. map.put("translateY", unit(val[1], "px"));
  183. }
  184. if (has3d && val.length > 2 && val[2] != null) {
  185. map.put("translateZ", unit(val[2], "px"));
  186. }
  187. } else {
  188. map.put(prop, Arrays.asList(val));
  189. }
  190. }
  191. /**
  192. * Converts the dictionary to a transition css string value but
  193. * excluding 3d properties if the browser only supports 2d.
  194. */
  195. public String toString() {
  196. // purposely using string addition, since my last tests demonstrate
  197. // that string addition performs better than string builders in gwt-prod.
  198. String ret = "";
  199. for (Entry<String, List<String>> e: map.entrySet()) {
  200. if (has3d || !transform3dRegex.test(e.getKey())) {
  201. String v = listToStr(e.getValue(), ",");
  202. ret += (ret.isEmpty() ? "" : " ") + e.getKey() + "(" + v + ")";
  203. }
  204. }
  205. return ret;
  206. }
  207. private List<String> unit(String val, String unit) {
  208. return Arrays.asList(val + (val.endsWith(unit) ? "" : unit));
  209. }
  210. }