Web Design Part 2: Adding Scrolling Frame Features


In Cased You Missed Previous Tutorial(s):

Web Design Part 1: Rounded Corner Hover Effect


Hello developers! :wave:

Majority of you voted for more tutorials on the previous one, so here it is! If you do not know already, this tutorial is dedicated to teaching you how to create certain aspects of websites. They were not wrong when they said anything is possible in Roblox (unless it is unsupported by the engine, but we leave this part out when talking to non-Robloxians :wink:).


Disclaimers / Knowledge Required :exclamation:

1

This technique only works without the ScrollBar. So please set ScrollBarThickness to 0.

2

Tween Service knowledge is required.


Overview :newspaper:

Today, we’ll be incorporating useful features to Scrolling Frames. In total there are exactly 3 of these custom features:

  1. Topbar collapsing when scrolling up
  2. Simple smooth scroll
  3. Outline (click to scroll to sections and more!)


As you can see, the blue “Shop” topbar on the top tweens out when scrolled down, but appears when scrolled up. Along with the smooth scrolling, notice the outline at the right showing the sections of the Shop. When I scroll to that section, the blue select moves to the name of that section in the outline. When I click on the button in the outline, the frame scrolls to that section.

This can be very useful especially when you have a lot of content in your Scrolling Frame. Players can easily skip to a section and the overall UX (user experience) improves when these additions.


Setup :hammer_and_wrench:

OK, so the overall layout is a bit complex but hopefully this image gives you an idea of where things should go. Of course, you do not need the exact sizes and positions, only the layout is important.


(The color of the box next to objects in the Explorer is represented in the Viewport by the same colored border).

This is how I created the basic layout. You do not have to get the exact sizes expect for things that have to cover the whole screen like #1.

Step-By-Step Setup

  1. Add a Frame to the ScreenGui; properties BackgroundTransparency and Size to 1 and {1, 0, 1, 0}, respectively.
  2. Add a Frame to the “Frame” from #1; rename it to “Topbar”, resize it to {1, 0, 0, 75}, and color it to anything you want.
  3. Add a text label inside the Topbar, format it as you desire.
  4. Add a Frame to the Frame (#1); rename it to “Outline”, resize it to {0.15, 0, 1, -75} (-75 because of the Topbar size), and set AnchorPoint to (1,1)
  5. Add TextButtons inside Outline; renaming them and formatting them as you wish (mine are Gear, Pets, and Perks).
  6. Add a Frame to the Outline; rename it to “Selected” and resize it to {0, 5, 0, 50}.
  7. Add a ScrollingFrame to ScreenGui; rename it to “Scrolling”, resize it to {0.85, 0},{1, -75}, set CanvasSize to {0, 0, 0, 2500}, AnchorPoint to (1,1), and ScrollingEnabled to false.
  8. Add a TextButton to the ScreenGui; rename it to “ScrollDetector”, BackgroundTransparency to 1, same exact properties as Scrolling.
  9. Add TextLabels inside of “Scrolling”; rename them to the exact names of the buttons in the oulintes (this is essential).
  10. Add a Bool Value inside of “Scrolling”; rename it to “CollapseExpand”.

I apologize if that sounds confusing, but the above it a must for this to work. I cannot cover everything in that UI, it would take extremely long. But this is the overall structure.

Add Local Scripts

  • Add three Local Scripts inside of the main Scrolling Frame, renaming them to “PlatformSetter”, “ScrollingOutline”, and “ScrollingScript”.
  • Add a Local Script in “PlatformSetter” and rename it to “Keyboard”.
  • Add Local Scripts (one in each of the TextButtons in the Outline), and rename them to “ClickToScroll”. Make sure to right click on it, convert to package, and select “AutoUpdate” in the package link parented to the script.

Scripting :scroll:

Smooth Scrolling

Open the “Keyboard” script. Let us start by defining variables:

local sFrame = script.Parent.Parent --main scrolling frame
local frame = sFrame.Parent
local ce = sFrame:WaitForChild("CollapseExpand") --bool value
local sDetec = frame:WaitForChild("ScrollDetector") --text button (used to detect mouse wheel actions)
local ts = game:GetService("TweenService")
local ti = TweenInfo.new(0.5, Enum.EasingStyle.Quint, Enum.EasingDirection.Out)
local player = game.Players.LocalPlayer
local ui = player:WaitForChild("PlayerGui")
local sd = ui:WaitForChild("ScrollDistance") --this is a Number Value I added to StarterGui, but this is basically a number representing how far to scroll each time
local y = sFrame.CanvasPosition.Y

Next, create this function that will set the value of “CollapseExpand”. This will prove useful later on:

local function setVal(bool)
	
	ce.Value = bool
	
end

Then, the main part of the script:

sDetec.MouseWheelBackward:Connect(function()
	
	setVal(false) --basically, to temporarily stop the topbar from collapse WHILE the scrolling tween is executed 
	
	if sFrame.CanvasPosition.Y <= (sFrame.CanvasSize.Y.Offset - sd.Value) then --if the scrolling frame is not close to the end 
		
		print("not close to end!")
		
		y = y + sd.Value
		
       --setting the tween
		local sDown = {CanvasPosition = Vector2.new(0, y)}
		local downCreate = ts:Create(sFrame, ti, sDown)
	
		downCreate:Play()
		
		downCreate.Completed:Connect(function() --when completed, the topbar collapses
			
			setVal(true)
			
		end)
		
	elseif sFrame.CanvasPosition.Y > (sFrame.CanvasSize.Y.Offset - sd.Value) then --if the scrolling frame is closer to the end than (the size of the canvas size minus the scroll distance)
		
		print("pretty close to end!")
		
		y = y + (sFrame.CanvasSize.Y.Offset - sFrame.CanvasPosition.Y) --add to y so that the scroll reaches the end
		
		local sDown = {CanvasPosition = Vector2.new(0, y)}
		local downCreate = ts:Create(sFrame, ti, sDown)
		
		downCreate:Play()
		
		downCreate.Completed:Connect(function()
			
			setVal(true) --topbar can now collapse
			
		end)
	
	end
		
end)

sDetec.MouseWheelForward:Connect(function()
	
	setVal(false)
	
	if sFrame.CanvasPosition.Y >= sd.Value then --far from the beginning
		
		print("not close to beg!")
		
		y = y - sd.Value
	
		local sUp = {CanvasPosition = Vector2.new(0, y)} --scroll down
		local upCreate = ts:Create(sFrame, ti, sUp)
		
		upCreate:Play()
		
		upCreate.Completed:Connect(function()
			
			setVal(true) --topbar can now expand
			
		end)
		
	elseif sFrame.AbsolutePosition.Y < sd.Value then --pretty close to the beginning
		
		print("pretty close to beg!")
		
		y = y - sFrame.CanvasPosition.Y
	
		local sUp = {CanvasPosition = Vector2.new(0, y)}
		local upCreate = ts:Create(sFrame, ti, sUp)
		
		upCreate:Play()
		
		upCreate.Completed:Connect(function()
			
			setVal(true)
			
		end)
	
	end
	
end)

So this is a simple version of Smooth Scrolling. We have created a custom scrolling script!

Device Detection

Open the “PlatformSetter” script and simply paste this in:

local uis = game:GetService("UserInputService")
local keyboard = script.Keyboard
local isTouch = uis.TouchEnabled
local isKeyboard = uis.KeyboardEnabled
keyboard.Disabled = true

if isKeyboard == true then

    keyboard.Disabled = false

end

script.Disabled = true

Expanding / Collapsing Topbar

Open the “ScrollingScript” and define these following variables:

local sFrame = script.Parent
local ce = sFrame:WaitForChild("CollapseExpand")
local frame = sFrame.Parent
local sd = frame:WaitForChild("ScrollDetector")
local tb = frame:WaitForChild("Topbar")
local outline = frame:WaitForChild("Outline")
local player = game.Players.LocalPlayer
local ui = player:WaitForChild("PlayerGui")
local scDis = ui:WaitForChild("ScrollDistance")
local cp = sFrame.CanvasPosition.Y
local dur = 0.25
local style = "Linear"
local dir = "Out"
local isOut = false --if topbar is expanded or not

Below, we detect if the Collapse Expand value changes, that’s our cue to tween the topbar!

ce.Changed:Connect(function()
	
	if ce.Value == true then --this will only run when the smooth scroll is done
	
		local cp2 = sFrame.CanvasPosition.Y --new position of canvas
		
		if cp2 > cp and isOut == false then --if they scrolled down, then expand the topbar
			
			tb:TweenPosition(UDim2.new(tb.Position.X.Scale, 0, tb.Position.Y.Scale, -(tb.Size.Y.Offset)), dir, style, dur, true)
			outline:TweenSize(UDim2.new(outline.Size.X.Scale, 0, outline.Size.Y.Scale, 0), dir, style, dur, true)
			sFrame:TweenSize(UDim2.new(sFrame.Size.X.Scale, 0, sFrame.Size.Y.Scale, 0), dir, style, dur, true)
			sd:TweenSize(UDim2.new(sd.Size.X.Scale, 0, sd.Size.Y.Scale, 0), dir, style, dur, true) --we also need to scale the scroll detector so that it is the same size as the scrolling frame
			isOut = true 
			
		elseif cp2 < cp and isOut == true then --collapse it
			
			tb:TweenPosition(UDim2.new(0, 0, 0, 0), dir, style, dur, true)	
			outline:TweenSize(UDim2.new(outline.Size.X.Scale, 0, outline.Size.Y.Scale, -tb.Size.Y.Offset), dir, style, dur, true)
			sFrame:TweenSize(UDim2.new(sFrame.Size.X.Scale, 0, sFrame.Size.Y.Scale, -tb.Size.Y.Offset), dir, style, dur, true)
			sd:TweenSize(UDim2.new(sd.Size.X.Scale, 0, sd.Size.Y.Scale, -tb.Size.Y.Offset), dir, style, dur, true)
			isOut = false
			
		end
		
		cp = cp2
	
	end
	
end)

Now, the Topbar tweens out when scrolling down and tweens in when scrolling up.

Select Outline Buttons Based On The Canvas Position

This is almost the last part of scripting in this tutorial. This is where we detect if the player has scrolled to a certain area and then we highlight what section they are in in the Outline. Open “ScrollOutline” and define:

local sFrame = script.Parent
local frame = sFrame.Parent
local outline = frame:WaitForChild("Outline")
local sc = outline:WaitForChild("Scrolling")
local selected = sc:WaitForChild("Selected")
local sections = {"Gear", "Pets", "Perks"} --make sure these are in order from top to bottom!

Second, we check if the player has scrolled to a certain region:

sFrame:GetPropertyChangedSignal("CanvasPosition"):Connect(function()
	
	local sOne = sFrame[sections[1]] --get the Text object inside of the scrolling frame
	local sTwo = sFrame[sections[2]]
	local sThree = sFrame[sections[3]]
	
	if sFrame.CanvasPosition.Y >= sOne.Position.Y.Offset and sFrame.CanvasPosition.Y < sTwo.Position.Y.Offset then --within first section
		
		print("one")
		selected.Position = UDim2.new(selected.Position.X.Scale, 0, 0, sc[sOne.Name].Position.Y.Offset)
		
	elseif sFrame.CanvasPosition.Y >= sTwo.Position.Y.Offset and sFrame.CanvasPosition.Y < sThree.Position.Y.Offset then --within second section
		
		print("two")
		selected.Position = UDim2.new(selected.Position.X.Scale, 0, 0, sc[sTwo.Name].Position.Y.Offset)
		
	elseif sFrame.CanvasPosition.Y >= sThree.Position.Y.Offset then
		
		print("three")
		selected.Position = UDim2.new(selected.Position.X.Scale, 0, 0, sc[sThree.Name].Position.Y.Offset)
		
	end
	
end)

Click To Scroll

Last part of the tutorial! Thank you for reading this far (or you just skipped here). Anyways, click on the local script that we coverted to package (it is called “ClickToScroll”).

local button = script.Parent --button in the outline
local scrolling = button.Parent --ignore this scrolling frame inside of the Outline
local outline = scrolling.Parent
local frame = outline.Parent
local sFrame = frame:WaitForChild("Scrolling")
local selected = scrolling:WaitForChild("Selected")
local pfSetter = sFrame:WaitForChild("PlatformSetter")
local ts = game:GetService("TweenService")
local ti = TweenInfo.new(0.5, Enum.EasingStyle.Linear, Enum.EasingDirection.Out)
local keyboard = pfSetter:WaitForChild("Keyboard")
local disabled = keyboard.Disabled --if the player is on a computer

Then, simply:

button.MouseButton1Click:Connect(function()
	
	if disabled == false then
		
		keyboard.Disabled = true --just so that while the script scrolls the frame, the player cannot
		
		local asocText = sFrame[button.Name] --the text label associated with the name of the button
		local tween = {CanvasPosition = Vector2.new(0, asocText.Position.Y.Offset)}
		local create = ts:Create(sFrame, ti, tween)
		
		create:Play()
		
		create.Completed:Connect(function()
			
			keyboard.Disabled = false --enable the script after scrolling for the player
			
		end)
		
	end
	
end)

Lastly, just duplicate this script as many times as there are buttons in the Outline (since I have three buttons (gear, pets, perks), I would duplicate the scripts 3 times and put one inside of each text button) like this:

OutlineExplorer

And we are done! We have added these features to the Scrolling Frame!


Full Scripts

“Keyboard” (smooth scrolling one):

local sFrame = script.Parent.Parent
local frame = sFrame.Parent
local ce = sFrame:WaitForChild("CollapseExpand")
local sDetec = frame:WaitForChild("ScrollDetector")
local ts = game:GetService("TweenService")
local ti = TweenInfo.new(0.5, Enum.EasingStyle.Quint, Enum.EasingDirection.Out)
local player = game.Players.LocalPlayer
local ui = player:WaitForChild("PlayerGui")
local sd = ui:WaitForChild("ScrollDistance")
local y = sFrame.CanvasPosition.Y

local function setVal(bool)
	
	ce.Value = bool
	
end

sDetec.MouseWheelBackward:Connect(function()
	
	setVal(false)
	
	if sFrame.CanvasPosition.Y <= (sFrame.CanvasSize.Y.Offset - sd.Value) then
		
		print("not close to end!")
		
		y = y + sd.Value
		
		local sDown = {CanvasPosition = Vector2.new(0, y)}
		local downCreate = ts:Create(sFrame, ti, sDown)
	
		downCreate:Play()
		
		downCreate.Completed:Connect(function()
			
			setVal(true)
			
		end)
		
	elseif sFrame.CanvasPosition.Y > (sFrame.CanvasSize.Y.Offset - sd.Value) then
		
		print("pretty close to end!")
		
		y = y + (sFrame.CanvasSize.Y.Offset - sFrame.CanvasPosition.Y)
		
		local sDown = {CanvasPosition = Vector2.new(0, y)}
		local downCreate = ts:Create(sFrame, ti, sDown)
		
		downCreate:Play()
		
		downCreate.Completed:Connect(function()
			
			setVal(true)
			
		end)
	
	end
		
end)

sDetec.MouseWheelForward:Connect(function()
	
	setVal(false)
	
	if sFrame.CanvasPosition.Y >= sd.Value then
		
		print("not close to beg!")
		
		y = y - sd.Value
	
		local sUp = {CanvasPosition = Vector2.new(0, y)}
		local upCreate = ts:Create(sFrame, ti, sUp)
		
		upCreate:Play()
		
		upCreate.Completed:Connect(function()
			
			setVal(true)
			
		end)
		
	elseif sFrame.AbsolutePosition.Y < sd.Value then
		
		print("pretty close to beg!")
		
		y = y - sFrame.CanvasPosition.Y
	
		local sUp = {CanvasPosition = Vector2.new(0, y)}
		local upCreate = ts:Create(sFrame, ti, sUp)
		
		upCreate:Play()
		
		upCreate.Completed:Connect(function()
			
			setVal(true)
			
		end)
	
	end
	
end)

“PlatformSetter” (device detection):

local uis = game:GetService("UserInputService")
local keyboard = script.Keyboard
local isTouch = uis.TouchEnabled
local isKeyboard = uis.KeyboardEnabled
keyboard.Disabled = true

if isKeyboard == true then

keyboard.Disabled = false

end

script.Disabled = true

“ScrollingScript” (topbar collapsing / expanding):

local sFrame = script.Parent
local ce = sFrame:WaitForChild("CollapseExpand")
local frame = sFrame.Parent
local sd = frame:WaitForChild("ScrollDetector")
local tb = frame:WaitForChild("Topbar")
local outline = frame:WaitForChild("Outline")
local player = game.Players.LocalPlayer
local ui = player:WaitForChild("PlayerGui")
local scDis = ui:WaitForChild("ScrollDistance")
local cp = sFrame.CanvasPosition.Y
local dur = 0.25
local style = "Linear"
local dir = "Out"
local isOut = false

ce.Changed:Connect(function()
	
	if ce.Value == true then
	
		local cp2 = sFrame.CanvasPosition.Y
		
		if cp2 > cp and isOut == false then 
			
			tb:TweenPosition(UDim2.new(tb.Position.X.Scale, 0, tb.Position.Y.Scale, -(tb.Size.Y.Offset)), dir, style, dur, true)
			outline:TweenSize(UDim2.new(outline.Size.X.Scale, 0, outline.Size.Y.Scale, 0), dir, style, dur, true)
			sFrame:TweenSize(UDim2.new(sFrame.Size.X.Scale, 0, sFrame.Size.Y.Scale, 0), dir, style, dur, true)
			sd:TweenSize(UDim2.new(sd.Size.X.Scale, 0, sd.Size.Y.Scale, 0), dir, style, dur, true)
			isOut = true
			
		elseif cp2 < cp and isOut == true then
			
			tb:TweenPosition(UDim2.new(0, 0, 0, 0), dir, style, dur, true)	
			outline:TweenSize(UDim2.new(outline.Size.X.Scale, 0, outline.Size.Y.Scale, -tb.Size.Y.Offset), dir, style, dur, true)
			sFrame:TweenSize(UDim2.new(sFrame.Size.X.Scale, 0, sFrame.Size.Y.Scale, -tb.Size.Y.Offset), dir, style, dur, true)
			sd:TweenSize(UDim2.new(sd.Size.X.Scale, 0, sd.Size.Y.Scale, -tb.Size.Y.Offset), dir, style, dur, true)
			isOut = false
			
		end
		
		cp = cp2
	
	end
	
end)

“ScrollOutline” (detect where player has scrolled to a certain region):

local sFrame = script.Parent
local frame = sFrame.Parent
local outline = frame.Outline
local sc = outline.Scrolling
local selected = sc:WaitForChild("Selected")
local sections = {"Gear", "Pets", "Perks"}

sFrame:GetPropertyChangedSignal("CanvasPosition"):Connect(function()
	
	local sOne = sFrame[sections[1]]
	local sTwo = sFrame[sections[2]]
	local sThree = sFrame[sections[3]]
	
	if sFrame.CanvasPosition.Y >= sOne.Position.Y.Offset and sFrame.CanvasPosition.Y < sTwo.Position.Y.Offset then --within first section
		
		print("one")
		selected.Position = UDim2.new(selected.Position.X.Scale, 0, 0, sc[sOne.Name].Position.Y.Offset)
		
	elseif sFrame.CanvasPosition.Y >= sTwo.Position.Y.Offset and sFrame.CanvasPosition.Y < sThree.Position.Y.Offset then --within second section
		
		print("two")
		selected.Position = UDim2.new(selected.Position.X.Scale, 0, 0, sc[sTwo.Name].Position.Y.Offset)
		
	elseif sFrame.CanvasPosition.Y >= sThree.Position.Y.Offset then
		
		print("three")
		selected.Position = UDim2.new(selected.Position.X.Scale, 0, 0, sc[sThree.Name].Position.Y.Offset)
		
	end
	
end)

“ClickToScroll” (click a button in the outline to scroll to the section):

local button = script.Parent
local scrolling = button.Parent
local outline = scrolling.Parent
local frame = outline.Parent
local sFrame = frame:WaitForChild("Scrolling")
local selected = scrolling:WaitForChild("Selected")
local pfSetter = sFrame:WaitForChild("PlatformSetter")
local ts = game:GetService("TweenService")
local ti = TweenInfo.new(0.5, Enum.EasingStyle.Linear, Enum.EasingDirection.Out)
local keyboard = pfSetter:WaitForChild("Keyboard")
local disabled = keyboard.Disabled

button.MouseButton1Click:Connect(function()
	
	if disabled == false then
		
		keyboard.Disabled = true
		
		local asocText = sFrame[button.Name]
		local tween = {CanvasPosition = Vector2.new(0, asocText.Position.Y.Offset)}
		local create = ts:Create(sFrame, ti, tween)
		
		create:Play()
		
		create.Completed:Connect(function()
			
			keyboard.Disabled = false
			
		end)
		
	end
	
end)

Feedback :speech_balloon:

How useful is this tutorial to you?

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

0 voters

OK, so I feel like certain parts of this post may seem and are hard to understand (like the Step-By-Step with a lot of numbers).
Is this tutorial confusing to understand at some points? Maybe a bit wordy? Please vote on how clear the information was. 1 being not clear at all and 10 being very clear.

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

0 voters

Additionally, feel free to notify me on how I can make my tutorial(s) better, my code better, and/or how much I should explain each step (if needed). Input on tutorial structure (headings etc.) and readability is appreciated!

Thank you for reading through this very long tutorial and for your feedback.

Next Tutorial(s)

14 Likes

Wow. Wow. Wow.
Amazing.
Do you have a .rbxl file I can download?

1 Like

I recommend this Module for smooth scrolling if you don’t want to make your own:

2 Likes

Yes that is also a possible choice, however showing how to accomplish smooth scrolling seemed to fit with the scrolling theme of this tutorial.

1 Like

Sure, this is the original place file I made this and the previous tutorial on.
Yes, I think I did this correctly…

UI.rbxl (31.9 KB)

Let me know if that works or not.

Thanks.
Looks like it should work.

1 Like

How it is with a gamepad? Should be treated the same as mouse yes?

1 Like

I am pretty sure you can implement the techniques used here for gamepad. But I haven’t done so currently.

This Is very helpfull! You could make part 3! (It’s your choice)

Keep It up! :+1:

1 Like