Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Added 3d-transforms-interactive-demo.html

  • Loading branch information...
commit 919fff3963783dc167c406b3ad136ac6e0c2f546 1 parent 42e2b11
Lars Kappert authored
365 css3/3d-transforms-interactive-demo.html
View
@@ -0,0 +1,365 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <title>CSS 3D Transforms (Interactive Demo)</title>
+ <style type="text/css">
+
+ /* Generic styles */
+
+ * {
+ box-sizing: border-box;
+ }
+
+ html, body {
+ height: 100%;
+ }
+
+ body {
+ font-family: Helvetica;
+ color: #333;
+ }
+
+ /* Canvas/stage styles */
+
+ #stage {
+ width: 100%;
+ height: 400px;
+ background-color: #EEE;
+ border: 1px solid #CCC;
+
+ perspective: 800px;
+ perspective-origin: 50% 50%;
+ }
+
+ /* Shape/face styles */
+
+ #shape {
+ position: relative;
+ top: 100px;
+ margin: 0 auto;
+ height: 200px;
+ width: 200px;
+ transform-style: preserve-3d;
+
+ animation-name: spin;
+ animation-iteration-count: infinite;
+ animation-timing-function:linear;
+ animation-duration: 8s;
+
+ }
+
+ .face {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ background-color: #333;
+ border: 1px solid #000;
+ opacity: 0.7;
+ color: #393939;
+ font-size: 10em;
+ }
+
+ .face:nth-child(1) {
+ transform: rotateY(0deg) translateZ(280px);
+ }
+ .face:nth-child(2) {
+ transform: rotateY(45deg) translateZ(280px);
+ }
+ .face:nth-child(3) {
+ transform: rotateY(90deg) translateZ(280px);
+ }
+ .face:nth-child(4) {
+ transform: rotateY(135deg) translateZ(280px);
+ }
+ .face:nth-child(5) {
+ transform: rotateY(180deg) translateZ(280px);
+ }
+ .face:nth-child(6) {
+ transform: rotateY(225deg) translateZ(280px);
+ }
+ .face:nth-child(7) {
+ transform: rotateY(270deg) translateZ(280px);
+ }
+ .face:nth-child(8) {
+ transform: rotateY(315deg) translateZ(280px);
+ }
+
+ /* Animations */
+
+ @keyframes spin {
+ from {
+ transform: rotateY(0);
+ }
+ to {
+ transform: rotateY(-360deg);
+ }
+ }
+
+ /* Form/configuration styles */
+
+ form {
+ margin-top: 1em;
+ }
+
+ fieldset {
+ display: inline-block;
+ vertical-align: top;
+ width: 450px;
+ border: 1px solid #333;
+ box-shadow: 3px 3px 3px #999;
+ border-radius: 5px;
+ margin-bottom: 10px;
+ }
+
+ input[type=range], output {
+ float: right;
+ }
+
+ input[type=range] {
+ margin: 2px 0 0;
+ }
+
+ output {
+ width: 100px;
+ text-align: right;
+ }
+
+ </style>
+
+ </head>
+ <body>
+ <h1>CSS 3D Transforms: Interactive Demo</h1>
+
+ <div id="stage">
+ <div id="shape">
+ <div class="face">1</div>
+ <div class="face">2</div>
+ <div class="face">3</div>
+ <div class="face">4</div>
+ <div class="face">5</div>
+ <div class="face">6</div>
+ <div class="face">7</div>
+ <div class="face">8</div>
+ </div>
+ </div>
+
+ <form id="config">
+
+ <fieldset>
+ <em>#stage</em>
+ <div>
+ <label>perspective (px)</label>
+ <input type="range" id="perspective" min="100" max="2000" value="800" data-target="#stage" data-unit="px">
+ </div>
+
+ <div>
+ <label>perspective-origin-x (%)</label>
+ <input type="range" id="perspective-origin-x" min="0" max="100" data-target="#stage" data-unit="%">
+ </div>
+
+ <div>
+ <label>perspective-origin-y (%)</label>
+ <input type="range" id="perspective-origin-y" min="0" max="100" data-target="#stage" data-unit="%">
+ </div>
+ </fieldset>
+
+ <fieldset>
+ <em>#shape (.face origin)</em>
+ <div>
+ transform-style:
+ <label>
+ <input type="radio" id="transform-style-preserve-3d" name="transform-style" value="preserve-3d" data-target="#shape" checked>
+ preserve 3d
+ </label>
+ <label>
+ <input type="radio" id="transform-style-flat" name="transform-style" value="flat" data-target="#shape">
+ flat
+ </label>
+ </div>
+ <div>
+ <label>animation-duration (s)</label>
+ <input type="range" id="animation-duration" min="1" max="20" value="8" data-target="#shape" data-unit="s">
+ </div>
+ <div>
+ visibility:
+ <label>
+ <input type="radio" id="background-color-transparent" name="background-color" value="transparent" data-target="#shape" checked>
+ transparent
+ </label>
+ <label>
+ <input type="radio" id="background-color-solid" name="background-color" value="#333" data-target="#shape">
+ solid
+ </label>
+ </div>
+ </fieldset>
+
+ <fieldset>
+ <em>.face (matrix.translate)</em>
+ <div>
+ <label>translateX (px)</label>
+ <input type="range" id="translateX" min="-280" max="280" data-target=".face" data-unit="px" data-transform="translateX">
+ </div>
+ <div>
+ <label>translateY (px)</label>
+ <input type="range" id="translateY" min="-280" max="280" data-target=".face" data-unit="px" data-transform="translateY">
+ </div>
+ <div>
+ <label>translateZ (px)</label>
+ <input type="range" id="translateZ" min="-280" max="280" data-target=".face" data-unit="px" data-transform="translateZ">
+ </div>
+ </fieldset>
+
+ <fieldset>
+ <em>.face (matrix.rotate)</em>
+ <div>
+ <label>rotateX (deg)</label>
+ <input type="range" id="rotateX" min="-180" max="180" value="0" data-target=".face" data-unit="deg" data-transform="rotateX">
+ </div>
+ <div>
+ <label>rotateY (deg)</label>
+ <input type="range" id="rotateY" min="-180" max="180" value="0" data-target=".face" data-unit="deg" data-transform="rotateY">
+ </div>
+ <div>
+ <label>rotateZ (deg)</label>
+ <input type="range" id="rotateZ" min="-180" max="180" value="0" data-target=".face" data-unit="deg" data-transform="rotateZ">
+ </div>
+ </fieldset>
+
+ <fieldset>
+ <em>.face</em>
+ <div>
+ <label>opacity</label>
+ <input type="range" id="opacity" min="0" max="1" step="0.1" value="0.7" data-target=".face" data-unit="">
+ </div>
+
+ <div>
+ backface-visibility:
+ <label>
+ <input type="radio" id="backface-visibility-visible" name="backface-visibility" value="visible" data-target=".face" checked>
+ visible
+ </label>
+ <label>
+ <input type="radio" id="backface-visibility-hidden" name="backface-visibility" value="hidden" data-target=".face">
+ hidden
+ </label>
+ </div>
+ </fieldset>
+
+ <input type="reset"/>
+
+ </form>
+
+ <script src="../resources/prefixfree.min.js"></script>
+ <script src="../resources/html5slider.js"></script>
+ <script src="../resources/lib.js"></script>
+
+ <script>
+
+ var configAll = $$('input[type=range], input[type=radio]');
+ var configRange = $$('input[type=range]');
+ var configReset = $$('input[type=range], input[type=radio]:checked');
+
+ var initConfigHandlers = function() {
+
+ [].forEach.call(configAll, function(element) {
+ element.addEventListener('change', function(event) {
+ updateAnimation(event.target);
+ }, false);
+ });
+ };
+
+ var updateAnimation = function(element) {
+
+ var units = element.getAttribute('data-unit') || '',
+ value = element.value,
+ previousValue = element._previousValue || 0,
+ property = element.name || element.id,
+ targets = $$(element.getAttribute('data-target')),
+ transform,
+ matrix;
+
+ [].forEach.call(targets, function(target) {
+ transform = element.getAttribute('data-transform');
+ if(transform) {
+
+ var MatrixImpl =
+ typeof WebKitCSSMatrix !== 'undefined' ? WebKitCSSMatrix :
+ typeof MozCSSMatrix !== 'undefined' ? MozCSSMatrix :
+ typeof MSCSSMatrix !== 'undefined' ? MSCSSMatrix :
+ typeof OCSSMatrix !== 'undefined' ? OCSSMatrix :
+ typeof CSSMatrix !== 'undefined' ? CSSMatrix :
+ null;
+
+ if(!MatrixImpl) {
+ return;
+ }
+
+ matrix = new MatrixImpl(window.getComputedStyle(target)[getStyleName('transform')]);
+
+ switch(transform) {
+ case 'translateX':
+ matrix = matrix.translate(value - previousValue, 0, 0);
+ break;
+ case 'translateY':
+ matrix = matrix.translate(0, value - previousValue, 0);
+ break;
+ case 'translateZ':
+ matrix = matrix.translate(0, 0, value - previousValue);
+ break;
+ case 'rotateX':
+ matrix = matrix.rotate(value - previousValue, 0, 0);
+ break;
+ case 'rotateY':
+ matrix = matrix.rotate(0, value - previousValue, 0);
+ break;
+ case 'rotateZ':
+ matrix = matrix.rotate(0, 0, value - previousValue);
+ break;
+ }
+
+ target.style[getStyleName('transform')] = matrix;
+
+ } else {
+
+ target.style[getStyleName(property)] = value + units;
+
+ }
+ })
+
+ element._previousValue = value;
+ }
+
+ var initOutputValues = function() {
+
+ [].forEach.call(configRange, function(element) {
+
+ element.addEventListener('change', function(event) {
+ element = event.target;
+ $('output[name=output_'+ element.id +']').value = element.value.substr(0, 11);
+ });
+
+ output = document.createElement('output');
+ output.name = 'output_' + element.id;
+ output.setAttribute('for', element.id);
+ output.defaultValue = output.value = element.value.substr(0, 11);
+
+ element.parentNode.insertBefore(output, element);
+ })
+ };
+
+ window.addEventListener('DOMContentLoaded', function() {
+ initConfigHandlers();
+ initOutputValues();
+ }, false);
+
+ $('#config').addEventListener('reset', function() {
+ [].forEach.call(configReset, function(element) {
+ updateAnimation(element)
+ });
+ }, false)
+
+ </script>
+
+ </body>
+</html>
8 index.html
View
@@ -57,6 +57,14 @@ <h1 class="beta">CSS3</h1>
<td><span class="support op"></span></td>
</tr>
<tr>
+ <td><a href="css3/3d-transforms-interactive-demo.html">3D Transforms: Interactive demo</a></td>
+ <td><span class="support ie"></span></td>
+ <td><span class="support ff supported"></span></td>
+ <td><span class="support gc supported"></span></td>
+ <td><span class="support sa supported"></span></td>
+ <td><span class="support op"></span></td>
+ </tr>
+ <tr>
<td><a href="css3/animations-101.html">Animations 101</a></td>
<td><span class="support ie"></span></td>
<td><span class="support ff supported"></span></td>
268 resources/html5slider.js
View
@@ -0,0 +1,268 @@
+/*
+html5slider - a JS implementation of <input type=range> for Firefox 4 and up
+https://github.com/fryn/html5slider
+
+Copyright (c) 2010-2011 Frank Yan, <http://frankyan.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+(function() {
+
+// test for native support
+var test = document.createElement('input');
+try {
+ test.type = 'range';
+ if (test.type == 'range')
+ return;
+} catch (e) {
+ return;
+}
+
+// test for required property support
+if (!document.mozSetImageElement || !('MozAppearance' in test.style))
+ return;
+
+var scale;
+var isMac = navigator.platform == 'MacIntel';
+var thumb = {
+ radius: isMac ? 9 : 6,
+ width: isMac ? 22 : 12,
+ height: isMac ? 15 : 20
+};
+var track = '-moz-linear-gradient(top, transparent ' + (isMac ?
+ '6px, #999 6px, #999 7px, #ccc 9px, #bbb 11px, #bbb 12px, transparent 12px' :
+ '9px, #999 9px, #bbb 10px, #fff 11px, transparent 11px') +
+ ', transparent)';
+var styles = {
+ 'min-width': thumb.width + 'px',
+ 'min-height': thumb.height + 'px',
+ 'max-height': thumb.height + 'px',
+ padding: 0,
+ border: 0,
+ 'border-radius': 0,
+ cursor: 'default',
+ 'text-indent': '-999999px' // -moz-user-select: none; breaks mouse capture
+};
+var onChange = document.createEvent('HTMLEvents');
+onChange.initEvent('change', true, false);
+
+if (document.readyState == 'loading')
+ document.addEventListener('DOMContentLoaded', initialize, true);
+else
+ initialize();
+
+function initialize() {
+ // create initial sliders
+ Array.forEach(document.querySelectorAll('input[type=range]'), transform);
+ // create sliders on-the-fly
+ document.addEventListener('DOMNodeInserted', onNodeInserted, true);
+}
+
+function onNodeInserted(e) {
+ check(e.target);
+ if (e.target.querySelectorAll)
+ Array.forEach(e.target.querySelectorAll('input'), check);
+}
+
+function check(input, async) {
+ if (input.localName != 'input' || input.type == 'range');
+ else if (input.getAttribute('type') == 'range')
+ transform(input);
+ else if (!async)
+ setTimeout(check, 0, input, true);
+}
+
+function transform(slider) {
+
+ var isValueSet, areAttrsSet, isChanged, isClick, prevValue, rawValue, prevX;
+ var min, max, step, range, value = slider.value;
+
+ // lazily create shared slider affordance
+ if (!scale) {
+ scale = document.body.appendChild(document.createElement('hr'));
+ style(scale, {
+ '-moz-appearance': isMac ? 'scale-horizontal' : 'scalethumb-horizontal',
+ display: 'block',
+ visibility: 'visible',
+ opacity: 1,
+ position: 'fixed',
+ top: '-999999px'
+ });
+ document.mozSetImageElement('__sliderthumb__', scale);
+ }
+
+ // reimplement value and type properties
+ var getValue = function() { return '' + value; };
+ var setValue = function setValue(val) {
+ value = '' + val;
+ isValueSet = true;
+ draw();
+ delete slider.value;
+ slider.value = value;
+ slider.__defineGetter__('value', getValue);
+ slider.__defineSetter__('value', setValue);
+ };
+ slider.__defineGetter__('value', getValue);
+ slider.__defineSetter__('value', setValue);
+ slider.__defineGetter__('type', function() { return 'range'; });
+
+ // sync properties with attributes
+ ['min', 'max', 'step'].forEach(function(prop) {
+ if (slider.hasAttribute(prop))
+ areAttrsSet = true;
+ slider.__defineGetter__(prop, function() {
+ return this.hasAttribute(prop) ? this.getAttribute(prop) : '';
+ });
+ slider.__defineSetter__(prop, function(val) {
+ val === null ? this.removeAttribute(prop) : this.setAttribute(prop, val);
+ });
+ });
+
+ // initialize slider
+ slider.readOnly = true;
+ style(slider, styles);
+ update();
+
+ slider.addEventListener('DOMAttrModified', function(e) {
+ // note that value attribute only sets initial value
+ if (e.attrName == 'value' && !isValueSet) {
+ value = e.newValue;
+ draw();
+ }
+ else if (~['min', 'max', 'step'].indexOf(e.attrName)) {
+ update();
+ areAttrsSet = true;
+ }
+ }, true);
+
+ slider.addEventListener('mousedown', onDragStart, true);
+ slider.addEventListener('keydown', onKeyDown, true);
+ slider.addEventListener('focus', onFocus, true);
+ slider.addEventListener('blur', onBlur, true);
+
+ function onDragStart(e) {
+ isClick = true;
+ setTimeout(function() { isClick = false; }, 0);
+ if (e.button || !range)
+ return;
+ var width = parseFloat(getComputedStyle(this, 0).width);
+ var multiplier = (width - thumb.width) / range;
+ if (!multiplier)
+ return;
+ // distance between click and center of thumb
+ var dev = e.clientX - this.getBoundingClientRect().left - thumb.width / 2 -
+ (value - min) * multiplier;
+ // if click was not on thumb, move thumb to click location
+ if (Math.abs(dev) > thumb.radius) {
+ isChanged = true;
+ this.value -= -dev / multiplier;
+ }
+ rawValue = value;
+ prevX = e.clientX;
+ this.addEventListener('mousemove', onDrag, true);
+ this.addEventListener('mouseup', onDragEnd, true);
+ }
+
+ function onDrag(e) {
+ var width = parseFloat(getComputedStyle(this, 0).width);
+ var multiplier = (width - thumb.width) / range;
+ if (!multiplier)
+ return;
+ rawValue += (e.clientX - prevX) / multiplier;
+ prevX = e.clientX;
+ isChanged = true;
+ this.value = rawValue;
+ }
+
+ function onDragEnd() {
+ this.removeEventListener('mousemove', onDrag, true);
+ this.removeEventListener('mouseup', onDragEnd, true);
+ }
+
+ function onKeyDown(e) {
+ if (e.keyCode > 36 && e.keyCode < 41) { // 37-40: left, up, right, down
+ onFocus.call(this);
+ isChanged = true;
+ this.value = value + (e.keyCode == 38 || e.keyCode == 39 ? step : -step);
+ }
+ }
+
+ function onFocus() {
+ if (!isClick)
+ this.style.boxShadow = !isMac ? '0 0 0 2px #fb0' :
+ '0 0 2px 1px -moz-mac-focusring, inset 0 0 1px -moz-mac-focusring';
+ }
+
+ function onBlur() {
+ this.style.boxShadow = '';
+ }
+
+ // determines whether value is valid number in attribute form
+ function isAttrNum(value) {
+ return !isNaN(value) && +value == parseFloat(value);
+ }
+
+ // validates min, max, and step attributes and redraws
+ function update() {
+ min = isAttrNum(slider.min) ? +slider.min : 0;
+ max = isAttrNum(slider.max) ? +slider.max : 100;
+ if (max < min)
+ max = min > 100 ? min : 100;
+ step = isAttrNum(slider.step) && slider.step > 0 ? +slider.step : 1;
+ range = max - min;
+ draw(true);
+ }
+
+ // recalculates value property
+ function calc() {
+ if (!isValueSet && !areAttrsSet)
+ value = slider.getAttribute('value');
+ if (!isAttrNum(value))
+ value = (min + max) / 2;;
+ // snap to step intervals (WebKit sometimes does not - bug?)
+ value = Math.round((value - min) / step) * step + min;
+ if (value < min)
+ value = min;
+ else if (value > max)
+ value = min + ~~(range / step) * step;
+ }
+
+ // renders slider using CSS background ;)
+ function draw(attrsModified) {
+ calc();
+ if (isChanged && value != prevValue)
+ slider.dispatchEvent(onChange);
+ isChanged = false;
+ if (!attrsModified && value == prevValue)
+ return;
+ prevValue = value;
+ var position = range ? (value - min) / range * 100 : 0;
+ var bg = '-moz-element(#__sliderthumb__) ' + position + '% no-repeat, ';
+ style(slider, { background: bg + track });
+ }
+
+}
+
+function style(element, styles) {
+ for (var prop in styles)
+ element.style.setProperty(prop, styles[prop], 'important');
+}
+
+})();
53 resources/lib.js
View
@@ -0,0 +1,53 @@
+var $ = function(selector) {
+ return document.querySelector(selector);
+}
+
+var $$ = function(selector) {
+ return document.querySelectorAll(selector);
+}
+
+// This function is taken from http://blog.stchur.com/2011/11/07/dealing-with-css-vendor-prefixes/
+
+var getStyleName = function () {
+ var styleHash = {};
+ var vendorPrefixes = [ 'O', 'ms', 'Moz', 'Webkit' ];
+ return function (styleName, elem) {
+ elem = elem || document.createElement('div');
+ var s = styleHash[styleName];
+ if (!s) {
+ // hope the browser supports the "official" (non-vendor-prefixed) style first
+ var camStyle = camelize(styleName);
+
+ // if not, then ask getVendorStyle for help -- it will try the various vendor prefixes seeking
+ // one that works (if it doesn't find one, it will return an empty string, in which case we
+ // revert back to originally passed in styleName)
+ if (elem.style[camStyle] === undefined) {
+ camStyle = getVendorStyle(camStyle, elem) || styleName;
+ }
+
+ // finally, store the camelized & vendor-specific (if applicable) value in the styleHash where key = styleName that was passed in
+ s = styleHash[styleName] = camStyle;
+ }
+ return s;
+ }
+
+ function getVendorStyle(style, elem) {
+ var s = camelize('-' + style);
+ var i = vendorPrefixes.length;
+ while (i--) {
+ style = vendorPrefixes[i] + s;
+ if (elem.style[style] !== undefined) {
+ return style;
+ }
+ }
+ return '';
+ }
+
+ ;
+
+ function camelize(s) {
+ return s.replace(/-(\w)/g, function (m, c) {
+ return c.toUpperCase();
+ });
+ }
+}();
Please sign in to comment.
Something went wrong with that request. Please try again.