Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Javascript

A Tale of Two CoverFlows

4.00/5 (1 vote)
18 Oct 2012CPOL8 min read 19.7K   291  
Two basic coverflow controls - one in JavaScript and one in Silverlight

CoverFlow Image

Introduction

I recently set myself the task of developing two versions of a simple coverflow control - one in Silverlight and one in JavaScript. In this article I will give a high level overview of the two implementations and discuss some of the similarities and differences I experienced while developing with the two technologies. It won't go into a lot of the details of the code. If you want to delve further into the code feel free to download the complete source for both implementations. The project is also hosted on github.

You can see the two controls in action here:

A caveat: The decision to develop the JavaScript version only for WebKit browsers enabled some short cuts which perhaps makes any comparison a little unfair. Obviously, to complete a more cross-browser friendly version requires more effort, whereas the Silverlight version should work in any browser that has an appropriate plug-in available. This needs to be kept in mind while comparing the code.

Contents

Implementation Overview

Both implementations take similar approaches. They start out with a collection of images laid out horizontally.

collection of images laid out, horiztonally, side-by-side

The "current" item is then scaled to bring it to the forefront, and its neighbouring items are scaled and rotated as shown below. All other items are hidden.

current item and neighbours transformed

A reflection is added to give the impression that the items are standing on a glass surface, rather than floating in air!

reflection added

The items are then moved horizontally to keep the current item central.

horizontally adjusted to keep the current item central

Finally, all of the above scaling, rotating and horizontal adjustment is animated to give the feeling of flipping through the items as the "current" item changes.

The Basic Layout

At the core of each implementation is a collection of items which is used to build up the UI.

JavaScript Version

For the JavaScript application I took the jQuery plugin approach.  The collection of items is passed in to the plugin as an array in settings.items.

Java
$.fn.coverFlow = function (settings) {
    settings = $.extend({}, $.fn.coverFlow.defaultSettings, settings || {});

    return this.each(function () {
        $.tmpl(settings.template, settings).appendTo(this);
        $("#coverFlowItems").width(settings.itemSize * settings.items.length);

        ...
    });
};

It uses jQuery templates to render the items. $.fn.coverFlow.defaultSettings provides defaults for the template and itemSize, so these can be omitted when calling the plugin or added to the settings object to override the defaults.

The default template renders the items as an unordered list, as shown below.

HTML
<ul id='coverFlowItems'>
  {{each $data.items}}
  <li>
    <a href='${$value.url}' tabindex='-1'>
      <img src="${$value.image}"
           width='${$data.itemSize}'
           height='${$data.itemSize}' />
    </a>
  </li>
  {{/each}}
</ul>

Then it just needs some css to lay out the items horizontally...

CSS
#coverFlowItems > li {
  display: inline-block;
  ...
}</

Silverlight Version

In the Silverlight version, the layout of the items is provided by an ItemsControl configured to use a horizontal StackPanel for its layout panel. The display of each item is delegated to a DataTemplate containing a UserControl of type CoverFlowItemView.

XML
...

<ItemsControl ItemsSource="{Binding CoverFlowItems}">
  <ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
      <StackPanel Orientation="Horizontal" />
    </ItemsPanelTemplate>
  </ItemsControl.ItemsPanel>
  <ItemsControl.ItemTemplate>
    <DataTemplate>
      <view:CoverFlowItemView />
    </DataTemplate>
  </ItemsControl.ItemTemplate>
</ItemsControl>
		
...

Using the Model-View-ViewModel pattern, the DataContext of the control is set to a view model object holding the collection of items in an ObservableCollection property called CoverFlowItems. The ItemsSource of the ItemsControl is bound to this property.

MVVM Class Diagram

The CoverFlowItemView user control defines the display of each item in a similar way to the jQuery template - an image with a hyperlink.

XML
<UserControl x:Class="CoverFlow.View.CoverFlowItemView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

  <UserControl.Resources>
    <!-- Styles & resources -->
    ...
  </UserControl.Resources>

  ...

  <HyperlinkButton NavigateUri="{Binding Url}">
    <Image Source="{Binding Image}"
        Style="{StaticResource coverFlowImageStyle}"/>
  </HyperlinkButton>

  ...

</UserControl>

Comparisons & Observations

An interesting aspect of the code I've shown so far is the impact of the differing type systems on the two implementations.

The jQuery plugin makes use of JavaScript's 'Duck Typing' - if the items passed in 'walk like a coverflow item and swim like a coverflow item and quack like a coverflow item, we can call them coverflow items!'  In other words, the plugin will be happy with any objects passed in as long as they have the expected image and url properties. In the Silverlight version, this requirement is explicitly defined in the CoverFlowItemViewModel type with its properties for Image and Url.

C#
public class CoverFlowItemViewModel : INotifyPropertyChanged
{
    ...

    private readonly string url;
    private readonly string image;

    public CoverFlowItemViewModel(string url, string image)
    {
        this.url = url;
        this.image = image;
    }

    public string Url { get { return url; } }
    public string Image { get { return image; } }

    ...
}

The relative advantages & disadvantages of static vs. dynamic type systems have been hotly debated. In practice, it comes down to a choice based on trade-offs as to which best suits the kind of application being developed.

In general, statically typed languages can provide better tooling support like auto-completion, code navigation, refactorings, etc., which can be a big productivity boost during development. Also, compile time error messages can help catch mistakes sooner.

On the other hand, the reduced edit-compile-test-debug cycle with dynamic languages can provide a more streamlined feedback & debugging experience. 

For an exercise of this nature, the static type checks and better tooling support available when developing the Silverlight version didn't seem to have as big an impact as they might on a larger more complex project. However, the streamlined feedback & debugging experience of the JavaScript development did make a big difference.

Being able to change some JavaScript and simply refresh the browser to see the effect of the changes is a big advantage. Also, using the in-browser debugging tools to examine the state of elements and experiment with different values for their attributes while the UI updates 'on the fly' helped to quickly understand & solve many problems.

I ended up using this to experiment with the appearance of the items in the JavaScript version and then use what I learned to make the same appearance in the Silverlight version.

in-browser debugging experience

Transforming the items

Once we've got the collection of items in a basic layout, the bulk of the rest of the code is concerned with transforming those items into the coverflow formation and animating this as the current item changes.

JavaScript Version

In the JavaScript version, the responsibility of the code is mainly to add and remove class names to the relevant elements in response to the current item changing. In the following code snippet _items is a collection of jQuery wrapped objects representing the item DOM elements, i.e., $("#coverFlowItems li").

Java
// Remove the class names from the old current item and its neighbours...
$(this._items[oldIndex - 2]).removeClass("left-2");
$(this._items[oldIndex - 1]).removeClass("left-1");
$(this._items[oldIndex]).removeClass("active");
$(this._items[oldIndex + 1]).removeClass("right-1");
$(this._items[oldIndex + 2]).removeClass("right-2");

// Add the class names to the new one and its neighbours...
$(this._items[this._currentIndex - 2]).addClass("left-2");
$(this._items[this._currentIndex - 1]).addClass("left-1");
$(this._items[this._currentIndex]).addClass("active");
$(this._items[this._currentIndex + 1]).addClass("right-1");
$(this._items[this._currentIndex + 2]).addClass("right-2");

 The transforms themselves are defined in css.

#coverFlowItems > li.active {
    z-index: 2;
    opacity: 1;
    -webkit-transform: scale(2);
}

#coverFlowItems > li.left-1 {
    z-index: 1;
    opacity: 1;
    -webkit-transform: scale(1.75) rotateY(60deg);
}

#coverFlowItems > li.right-1 {
    z-index: 1;
    opacity: 1;
    -webkit-transform: scale(1.75) rotateY(-60deg);
}

#coverFlowItems > li.left-2 {
    opacity: 0.8;
    -webkit-transform: scale(1.5) rotateY(60deg);
}

#coverFlowItems > li.right-2 {
    opacity: 0.8;
    -webkit-transform: scale(1.5) rotateY(-60deg);
}

The animation is also defined in the css.

#coverFlowItems > li {
    ...
    -webkit-transition: all 0.5s;
}

Silverlight Version

To create the same effect in the Silverlight version I made use of the VisualState class to define the appearance of the items in each specific state.

XML
<VisualStateManager.VisualStateGroups>
    <VisualStateGroup>
        <VisualState x:Name="Left_2">
            <Storyboard TargetName="itemPanel">
                <DoubleAnimation
                    To="0.8"
                    Duration="0:00:00.5"
                    Storyboard.TargetProperty="(UIElement.Opacity)" />
                <DoubleAnimation
                    To="-60"
                    Duration="0:00:00.5"
                    Storyboard.TargetProperty="(UIElement.Projection).(RotationY)" />
                <DoubleAnimation
                    To="-0"
                    Duration="0:00:00.5"
                    Storyboard.TargetProperty="(UIElement.Projection)
                                              .(CenterOfRotationX)" />
                <DoubleAnimation
                    To="1.5"
                    Duration="0:00:00.5"
                    Storyboard.TargetProperty="(UIElement.RenderTransform)
                                              .Children[0].ScaleX" />
                <DoubleAnimation
                    To="1.5"
                    Duration="0:00:00.5"
                    Storyboard.TargetProperty="(UIElement.RenderTransform)
                                              .Children[0].ScaleY" />
            </Storyboard>
        </VisualState>
        <VisualState x:Name="Left_1">
            ...
        </VisualState>
        <VisualState x:Name="Current">
            ...
        </VisualState>
        <VisualState x:Name="Right_1">
            ...
        </VisualState>
        <VisualState x:Name="Right_2">
            ...
        </VisualState>
        <VisualState x:Name="Hidden">
            ...
        </VisualState>
    </VisualStateGroup>
</VisualStateManager.VisualStateGroups>

I created an attached property to allow the visual state of each item to be bound to a view model property.

C#
public static readonly DependencyProperty VisualStateProperty =
    DependencyProperty.RegisterAttached("VisualState", typeof(string),
    typeof(CoverFlowItemView), new PropertyMetadata(VisualStateChanged));

public static string GetVisualState(DependencyObject target)
{
    return (string)target.GetValue(VisualStateProperty);
}

public static void SetVisualState(DependencyObject target, string value)
{
    target.SetValue(VisualStateProperty, value);
}

private static void VisualStateChanged(object sender,
        DependencyPropertyChangedEventArgs args)
{
    var newState = (string)args.NewValue;
    if (!string.IsNullOrWhiteSpace(newState))
    {
        ...

        VisualStateManager.GoToState(control, newState, true);
    }
}

The binding between the CoverFlowItemView and the CoverFlowItemViewModel looks like this...

XML
<UserControl x:Class="CoverFlow.View.CoverFlowItemView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:CoverFlow.View" 
    local:CoverFlowItemView.VisualState="{Binding VisualState}">
C#
public class CoverFlowItemViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    ...

    private string visualState = "Hidden";
    public string VisualState
    {
        get { return visualState; }
        set
        {
            if (VisualState == value)
            {
                return;
            }

            visualState = value;
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs("VisualState"));
            }
        }
    }
}

So when the current item changes the items can be transformed by updating their VisualState appropriately, in a similar way to the JavaScript version, for example...

C#
public void NextItem()
{
    SetItemVisualState(CurrentItemIndex - 2, "Hidden");
    SetItemVisualState(CurrentItemIndex - 1, "Left_2");
    SetItemVisualState(CurrentItemIndex, "Left_1");
    SetItemVisualState(CurrentItemIndex + 1, "Current");
    SetItemVisualState(CurrentItemIndex + 2, "Right_1");
    SetItemVisualState(CurrentItemIndex + 3, "Right_2");

    CurrentItemIndex++;
}

private void SetItemVisualState(int itemIndex, string visualState)
{
    if (IsValidIndex(itemIndex))
    {
        CoverFlowItems[itemIndex].VisualState = visualState;
    }
}

Comparisons & Observations

Conceptually the two implementations are quite similar.  They are both based around a separation between the logic that determines the current state of each item and the definition of what that state should look like.

There can be no denying though that the Silverlight implementation is more verbose. Also, the paths in those Storyboard.TargetProperty settings (like the one shown below) took quite a bit of trial and error to get right and are tricky to debug when they're not working as expected!

XML
Storyboard.TargetProperty="(UIElement.RenderTransform).Children[0].ScaleX"

As mentioned at the beginning of the article though, it is largely down to those transform definitions in the css that make the JavaScript version WebKit specific.

CSS
-webkit-transform: scale(1.75) rotateY(60deg);

A lot of the vendor specific properties have equivalents in each of the main browsers, but they're not standard and not guaranteed to work the same way across browsers. (In fact, they're not guaranteed to work the same way in future releases of the same browser!)

A quick test in Firefox after replacing all -webkit- prefixes with -moz-, didn't quite work as expected so clearly there's more work required than just repeating the properties with different vendor prefixes!

Some welcome these vendor-specific properties as accelerating CSS development; others consider them to be harmful to web standards. Either way, an overriding advantage of the Silverlight version is that, having gone to the effort of implementing that more verbose solution, we can expect it to work in any browser that has a Silverlight plug-in available.

The Silverlight version runs in the major browsers...

silverlight version running in the major browsers

Conclusion

I have given quite a high-level overview here and discussed a couple of areas where there were interesting similarities or differences between the two implementations. There are some details in both versions that have been ignored for the sake of the article. The complete source is available if you want to look at the code in more detail.

If I was backed into a corner and forced to draw a conclusion from these comparisons I would have to say that, for this exercise, it seems the flexibility and dynamism of the JavaScript language led to a more succinct solution than the Silverlight version, and the in-browser debugging tools were a big time saver. The static type checks and other tooling available with Silverlight development (and the lack of them in the JavaScript development environment) didn't seem to make as big an impact as I expected. However, we can't ignore the fact that the Silverlight version provides a much wider browser coverage for no extra effort whereas there is still some work to be done in that respect with the JavaScript version.

Of course, it isn't difficult to imagine a scenario where the impact of these differences would be reversed - i.e., where the type checking and tooling available with Silverlight development would become much more important. A better way to look at the differences is to think of them as choices based on trade-offs and considered in the context of the type of application being developed, rather than an overall 'which is best' decision.

Finally, I can recommend trying this kind of exercise yourself (developing a simple application in two technologies and comparing the experience). It can be a useful tool to help understand these trade-offs and gain a better idea of the impact they might have on the kinds of applications you work with.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)